Emaıl templates.

This commit is contained in:
Burak Kaan Köse
2026-03-08 15:48:11 +01:00
parent 15400d4096
commit e4a224bd68
24 changed files with 541 additions and 15 deletions
@@ -0,0 +1,16 @@
using System;
using SQLite;
namespace Wino.Core.Domain.Entities.Mail;
public class EmailTemplate
{
[PrimaryKey]
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string HtmlContent { get; set; } = string.Empty;
}
+2
View File
@@ -35,6 +35,8 @@ public enum WinoPage
EventDetailsPage, EventDetailsPage,
CalendarEventComposePage, CalendarEventComposePage,
SignatureAndEncryptionPage, SignatureAndEncryptionPage,
EmailTemplatesPage,
CreateEmailTemplatePage,
StoragePage, StoragePage,
WelcomePageV2, WelcomePageV2,
WelcomeHostPage, WelcomeHostPage,
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities.Mail;
namespace Wino.Core.Domain.Interfaces;
public interface IEmailTemplateService
{
Task<List<EmailTemplate>> GetEmailTemplatesAsync();
Task<EmailTemplate> GetEmailTemplateAsync(Guid templateId);
Task<EmailTemplate> CreateEmailTemplateAsync(EmailTemplate template);
Task<EmailTemplate> UpdateEmailTemplateAsync(EmailTemplate template);
Task<EmailTemplate> DeleteEmailTemplateAsync(EmailTemplate template);
}
@@ -254,6 +254,8 @@
"DialogMessage_DeleteAccountConfirmationTitle": "All data associated with this account will be deleted from disk permanently.", "DialogMessage_DeleteAccountConfirmationTitle": "All data associated with this account will be deleted from disk permanently.",
"DialogMessage_DeleteRecurringSeriesMessage": "This will delete all events in the series. Do you want to continue?", "DialogMessage_DeleteRecurringSeriesMessage": "This will delete all events in the series. Do you want to continue?",
"DialogMessage_DeleteRecurringSeriesTitle": "Delete Recurring Series", "DialogMessage_DeleteRecurringSeriesTitle": "Delete Recurring Series",
"DialogMessage_DeleteEmailTemplateConfirmationMessage": "Delete template \"{0}\"?",
"DialogMessage_DeleteEmailTemplateConfirmationTitle": "Delete Email Template",
"DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?", "DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?",
"DialogMessage_DiscardDraftConfirmationTitle": "Discard Draft", "DialogMessage_DiscardDraftConfirmationTitle": "Discard Draft",
"DialogMessage_EmptySubjectConfirmation": "Missing Subject", "DialogMessage_EmptySubjectConfirmation": "Missing Subject",
@@ -849,6 +851,19 @@
"SettingsShowSenderPictures_Title": "Show Sender Avatars", "SettingsShowSenderPictures_Title": "Show Sender Avatars",
"SettingsEnableGravatarAvatars_Title": "Gravatar", "SettingsEnableGravatarAvatars_Title": "Gravatar",
"SettingsEnableGravatarAvatars_Description": "Use gravatar (if available) as sender picture", "SettingsEnableGravatarAvatars_Description": "Use gravatar (if available) as sender picture",
"SettingsEmailTemplates_Title": "Email Templates",
"SettingsEmailTemplates_Description": "Manage e-mail templates",
"SettingsEmailTemplates_CreatePageTitle": "New template",
"SettingsEmailTemplates_EditPageTitle": "Edit template",
"SettingsEmailTemplates_NewTemplateTitle": "New template",
"SettingsEmailTemplates_NewTemplateDescription": "Create a new e-mail template",
"SettingsEmailTemplates_NameTitle": "Name",
"SettingsEmailTemplates_NamePlaceholder": "Template name",
"SettingsEmailTemplates_DescriptionTitle": "Description",
"SettingsEmailTemplates_DescriptionPlaceholder": "Optional description",
"SettingsEmailTemplates_ContentTitle": "Template content",
"SettingsEmailTemplates_ContentDescription": "Edit the HTML content for this template.",
"SettingsEmailTemplates_NameRequired": "Template name is required.",
"SettingsEnableFavicons_Title": "Domain icons (Favicons)", "SettingsEnableFavicons_Title": "Domain icons (Favicons)",
"SettingsEnableFavicons_Description": "Use domain favicons (if available) as sender picture", "SettingsEnableFavicons_Description": "Use domain favicons (if available) as sender picture",
"SettingsMailList_ClearAvatarsCache_Button": "Clear cached avatars", "SettingsMailList_ClearAvatarsCache_Button": "Clear cached avatars",
@@ -1006,6 +1021,7 @@
"Composer_CertificateExpires": "Expires on: ", "Composer_CertificateExpires": "Expires on: ",
"Composer_SmimeSignature": "S/MIME Signature", "Composer_SmimeSignature": "S/MIME Signature",
"Composer_SmimeEncryption": "S/MIME Encryption", "Composer_SmimeEncryption": "S/MIME Encryption",
"Composer_EmailTemplatesPlaceholder": "E-mail templates",
"SettingsAppPreferences_EmailSyncInterval_Title": "Email sync interval", "SettingsAppPreferences_EmailSyncInterval_Title": "Email sync interval",
"SettingsAppPreferences_EmailSyncInterval_Description": "Automatic email synchronization interval (minutes). This setting will be applied only after restarting Wino Mail.", "SettingsAppPreferences_EmailSyncInterval_Description": "Automatic email synchronization interval (minutes). This setting will be applied only after restarting Wino Mail.",
"ContactsPage_Title": "Contacts", "ContactsPage_Title": "Contacts",
@@ -76,6 +76,7 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title, WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title,
WinoPage.LanguageTimePage => Translator.SettingsLanguageTime_Title, WinoPage.LanguageTimePage => Translator.SettingsLanguageTime_Title,
WinoPage.AppPreferencesPage => Translator.SettingsAppPreferences_Title, WinoPage.AppPreferencesPage => Translator.SettingsAppPreferences_Title,
WinoPage.EmailTemplatesPage => Translator.SettingsEmailTemplates_Title,
WinoPage.CalendarSettingsPage => Translator.SettingsCalendarSettings_Title, WinoPage.CalendarSettingsPage => Translator.SettingsCalendarSettings_Title,
WinoPage.SignatureAndEncryptionPage => Translator.SettingsSignatureAndEncryption_Title, WinoPage.SignatureAndEncryptionPage => Translator.SettingsSignatureAndEncryption_Title,
WinoPage.KeyboardShortcutsPage => Translator.Settings_KeyboardShortcuts_Title, WinoPage.KeyboardShortcutsPage => Translator.Settings_KeyboardShortcuts_Title,
@@ -130,6 +130,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
public bool AreCertificatesAvailable => AvailableCertificates.Count > 0; public bool AreCertificatesAvailable => AvailableCertificates.Count > 0;
public ObservableCollection<EmailTemplate> AvailableEmailTemplates { get; } = [];
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = []; public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = [];
public ObservableCollection<MailAccount> Accounts { get; set; } = []; public ObservableCollection<MailAccount> Accounts { get; set; } = [];
public ObservableCollection<AccountContact> ToItems { get; set; } = []; public ObservableCollection<AccountContact> ToItems { get; set; } = [];
@@ -148,6 +149,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
private readonly IFileService _fileService; private readonly IFileService _fileService;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly IEmailTemplateService _emailTemplateService;
private readonly IWinoRequestDelegator _worker; private readonly IWinoRequestDelegator _worker;
public readonly IFontService FontService; public readonly IFontService FontService;
public readonly IPreferencesService PreferencesService; public readonly IPreferencesService PreferencesService;
@@ -161,6 +163,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
INativeAppService nativeAppService, INativeAppService nativeAppService,
IFolderService folderService, IFolderService folderService,
IAccountService accountService, IAccountService accountService,
IEmailTemplateService emailTemplateService,
IWinoRequestDelegator worker, IWinoRequestDelegator worker,
IContactService contactService, IContactService contactService,
IFontService fontService, IFontService fontService,
@@ -178,6 +181,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
_mimeFileService = mimeFileService; _mimeFileService = mimeFileService;
_fileService = fileService; _fileService = fileService;
_accountService = accountService; _accountService = accountService;
_emailTemplateService = emailTemplateService;
_worker = worker; _worker = worker;
_smimeCertificateService = smimeCertificateService; _smimeCertificateService = smimeCertificateService;
@@ -520,6 +524,7 @@ public partial class ComposePageViewModel : MailBaseViewModel,
CurrentMailDraftItem = mailItem; CurrentMailDraftItem = mailItem;
await UpdatePendingOperationStateAsync(); await UpdatePendingOperationStateAsync();
await LoadEmailTemplatesAsync();
await TryPrepareComposeAsync(true); await TryPrepareComposeAsync(true);
} }
} }
@@ -539,9 +544,25 @@ public partial class ComposePageViewModel : MailBaseViewModel,
// Set the new draft item and prepare it. // Set the new draft item and prepare it.
CurrentMailDraftItem = message.MailItemViewModel; CurrentMailDraftItem = message.MailItemViewModel;
await UpdatePendingOperationStateAsync(); await UpdatePendingOperationStateAsync();
await LoadEmailTemplatesAsync();
await TryPrepareComposeAsync(true); await TryPrepareComposeAsync(true);
} }
private async Task LoadEmailTemplatesAsync()
{
var templates = await _emailTemplateService.GetEmailTemplatesAsync().ConfigureAwait(false);
await ExecuteUIThread(() =>
{
AvailableEmailTemplates.Clear();
foreach (var template in templates)
{
AvailableEmailTemplates.Add(template);
}
});
}
public async void Receive(SynchronizationActionsAdded message) public async void Receive(SynchronizationActionsAdded message)
{ {
if (!ShouldTrackDraftSynchronizationState(message.AccountId)) if (!ShouldTrackDraftSynchronizationState(message.AccountId))
@@ -0,0 +1,112 @@
using System;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
namespace Wino.Mail.ViewModels;
public partial class CreateEmailTemplatePageViewModel(
IEmailTemplateService emailTemplateService,
IMailDialogService dialogService,
INavigationService navigationService) : MailBaseViewModel
{
private readonly IEmailTemplateService _emailTemplateService = emailTemplateService;
private readonly IMailDialogService _dialogService = dialogService;
private EmailTemplate _editingTemplate;
public INavigationService NavigationService { get; } = navigationService;
[ObservableProperty]
public partial string TemplateName { get; set; } = string.Empty;
[ObservableProperty]
public partial string TemplateDescription { get; set; } = string.Empty;
[ObservableProperty]
public partial bool IsExistingTemplate { get; set; }
public async Task<string> LoadAsync(object parameter)
{
EmailTemplate template = null;
var templateId = parameter switch
{
Guid guid when guid != Guid.Empty => guid,
string value when Guid.TryParse(value, out var parsedGuid) => parsedGuid,
EmailTemplate emailTemplate when emailTemplate.Id != Guid.Empty => emailTemplate.Id,
_ => Guid.Empty
};
if (templateId != Guid.Empty)
{
template = await _emailTemplateService.GetEmailTemplateAsync(templateId).ConfigureAwait(false);
}
_editingTemplate = template;
await ExecuteUIThread(() =>
{
IsExistingTemplate = template != null;
TemplateName = template?.Name ?? string.Empty;
TemplateDescription = template?.Description ?? string.Empty;
});
return template?.HtmlContent ?? string.Empty;
}
public async Task SaveAsync(string htmlContent)
{
var trimmedName = TemplateName?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(trimmedName))
{
_dialogService.InfoBarMessage(
Translator.GeneralTitle_Error,
Translator.SettingsEmailTemplates_NameRequired,
InfoBarMessageType.Warning);
return;
}
var template = _editingTemplate ?? new EmailTemplate
{
Id = Guid.NewGuid()
};
template.Name = trimmedName;
template.Description = TemplateDescription?.Trim() ?? string.Empty;
template.HtmlContent = htmlContent ?? string.Empty;
if (_editingTemplate == null)
{
await _emailTemplateService.CreateEmailTemplateAsync(template).ConfigureAwait(false);
}
else
{
await _emailTemplateService.UpdateEmailTemplateAsync(template).ConfigureAwait(false);
}
_editingTemplate = template;
NavigationService.GoBack();
}
public async Task DeleteAsync()
{
if (_editingTemplate == null)
return;
var shouldDelete = await _dialogService.ShowConfirmationDialogAsync(
string.Format(Translator.DialogMessage_DeleteEmailTemplateConfirmationMessage, _editingTemplate.Name),
Translator.DialogMessage_DeleteEmailTemplateConfirmationTitle,
Translator.Buttons_Delete).ConfigureAwait(false);
if (!shouldDelete)
return;
await _emailTemplateService.DeleteEmailTemplateAsync(_editingTemplate).ConfigureAwait(false);
NavigationService.GoBack();
}
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Messaging;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Messaging.Client.Navigation;
namespace Wino.Mail.ViewModels;
public partial class EmailTemplatesPageViewModel(IEmailTemplateService emailTemplateService) : MailBaseViewModel
{
private readonly IEmailTemplateService _emailTemplateService = emailTemplateService;
public ObservableCollection<EmailTemplate> EmailTemplates { get; } = [];
public async Task LoadAsync()
{
var templates = await _emailTemplateService.GetEmailTemplatesAsync().ConfigureAwait(false);
await ExecuteUIThread(() =>
{
EmailTemplates.Clear();
foreach (var template in templates)
{
EmailTemplates.Add(template);
}
});
}
public void CreateTemplate()
{
Messenger.Send(new BreadcrumbNavigationRequested(
Translator.SettingsEmailTemplates_CreatePageTitle,
WinoPage.CreateEmailTemplatePage));
}
public void OpenTemplate(EmailTemplate template)
{
if (template == null)
return;
var title = string.IsNullOrWhiteSpace(template.Name)
? Translator.SettingsEmailTemplates_EditPageTitle
: template.Name;
Messenger.Send(new BreadcrumbNavigationRequested(
title,
WinoPage.CreateEmailTemplatePage,
template.Id));
}
}
+2
View File
@@ -167,6 +167,8 @@ public partial class App : WinoApplication,
services.AddTransient(typeof(AliasManagementPageViewModel)); services.AddTransient(typeof(AliasManagementPageViewModel));
services.AddTransient(typeof(ContactsPageViewModel)); services.AddTransient(typeof(ContactsPageViewModel));
services.AddTransient(typeof(SignatureAndEncryptionPageViewModel)); services.AddTransient(typeof(SignatureAndEncryptionPageViewModel));
services.AddTransient(typeof(EmailTemplatesPageViewModel));
services.AddTransient(typeof(CreateEmailTemplatePageViewModel));
services.AddTransient(typeof(CalendarPageViewModel)); services.AddTransient(typeof(CalendarPageViewModel));
services.AddTransient(typeof(CalendarSettingsPageViewModel)); services.AddTransient(typeof(CalendarSettingsPageViewModel));
services.AddTransient(typeof(CalendarAccountSettingsPageViewModel)); services.AddTransient(typeof(CalendarAccountSettingsPageViewModel));
@@ -120,6 +120,8 @@ public class NavigationService : NavigationServiceBase, INavigationService
WinoPage.KeyboardShortcutsPage => typeof(KeyboardShortcutsPage), WinoPage.KeyboardShortcutsPage => typeof(KeyboardShortcutsPage),
WinoPage.ContactsPage => typeof(ContactsPage), WinoPage.ContactsPage => typeof(ContactsPage),
WinoPage.SignatureAndEncryptionPage => typeof(SignatureAndEncryptionPage), WinoPage.SignatureAndEncryptionPage => typeof(SignatureAndEncryptionPage),
WinoPage.EmailTemplatesPage => typeof(EmailTemplatesPage),
WinoPage.CreateEmailTemplatePage => typeof(CreateEmailTemplatePage),
WinoPage.StoragePage => typeof(StoragePage), WinoPage.StoragePage => typeof(StoragePage),
WinoPage.WelcomeHostPage => typeof(WelcomeHostPage), WinoPage.WelcomeHostPage => typeof(WelcomeHostPage),
WinoPage.ProviderSelectionPage => typeof(ProviderSelectionPage), WinoPage.ProviderSelectionPage => typeof(ProviderSelectionPage),
@@ -0,0 +1,7 @@
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class CreateEmailTemplatePageAbstract : SettingsPageBase<CreateEmailTemplatePageViewModel>
{
}
@@ -0,0 +1,7 @@
using Wino.Mail.ViewModels;
namespace Wino.Views.Abstract;
public abstract class EmailTemplatesPageAbstract : SettingsPageBase<EmailTemplatesPageViewModel>
{
}
+16 -3
View File
@@ -138,6 +138,21 @@
CommandAlignment="Right" CommandAlignment="Right"
IsDynamicOverflowEnabled="True" IsDynamicOverflowEnabled="True"
OverflowButtonAlignment="Left"> OverflowButtonAlignment="Left">
<AppBarElementContainer Visibility="{x:Bind helpers:XamlHelpers.CountToVisibilityConverter(ViewModel.AvailableEmailTemplates.Count), Mode=OneWay}">
<ComboBox
x:Name="EmailTemplatesComboBox"
MinWidth="220"
Margin="0,8,8,0"
ItemsSource="{x:Bind ViewModel.AvailableEmailTemplates, Mode=OneWay}"
PlaceholderText="{x:Bind domain:Translator.Composer_EmailTemplatesPlaceholder}"
SelectionChanged="EmailTemplateSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="mail:EmailTemplate">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</AppBarElementContainer>
<AppBarButton <AppBarButton
MinWidth="40" MinWidth="40"
HorizontalContentAlignment="Center" HorizontalContentAlignment="Center"
@@ -145,9 +160,7 @@
Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}"> Visibility="{x:Bind ViewModel.IsDraftBusy, Mode=OneWay}">
<ProgressRing IsActive="True" /> <ProgressRing IsActive="True" />
</AppBarButton> </AppBarButton>
<AppBarButton <AppBarButton Click="ToggleEditorThemeClicked" ToolTipService.ToolTip="{x:Bind GetEditorThemeToolTip(WebViewEditor.IsEditorDarkMode), Mode=OneWay}">
Click="ToggleEditorThemeClicked"
ToolTipService.ToolTip="{x:Bind GetEditorThemeToolTip(WebViewEditor.IsEditorDarkMode), Mode=OneWay}">
<AppBarButton.Icon> <AppBarButton.Icon>
<coreControls:WinoFontIcon Icon="{x:Bind GetEditorThemeIcon(WebViewEditor.IsEditorDarkMode), Mode=OneWay}" /> <coreControls:WinoFontIcon Icon="{x:Bind GetEditorThemeIcon(WebViewEditor.IsEditorDarkMode), Mode=OneWay}" />
</AppBarButton.Icon> </AppBarButton.Icon>
@@ -18,6 +18,7 @@ using Windows.Foundation;
using Windows.Storage; using Windows.Storage;
using Windows.UI.Core.Preview; using Windows.UI.Core.Preview;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Models.Reader; using Wino.Core.Domain.Models.Reader;
using Wino.Mail.ViewModels.Data; using Wino.Mail.ViewModels.Data;
@@ -53,6 +54,15 @@ public sealed partial class ComposePage : ComposePageAbstract,
WebViewEditor.ToggleEditorTheme(); WebViewEditor.ToggleEditorTheme();
} }
private async void EmailTemplateSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is not ComboBox comboBox || comboBox.SelectedItem is not EmailTemplate template)
return;
await WebViewEditor.RenderHtmlAsync(template.HtmlContent);
comboBox.SelectedItem = null;
}
private async void GlobalFocusManagerGotFocus(object? sender, FocusManagerGotFocusEventArgs e) private async void GlobalFocusManagerGotFocus(object? sender, FocusManagerGotFocusEventArgs e)
{ {
// In order to delegate cursor to the inner editor for WebView2. // In order to delegate cursor to the inner editor for WebView2.
+20 -12
View File
@@ -14,11 +14,14 @@
<ScrollViewer Padding="0,0,16,0" VerticalScrollBarVisibility="Auto"> <ScrollViewer Padding="0,0,16,0" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,8,0,24" Spacing="4"> <StackPanel Margin="0,8,0,24" Spacing="4">
<StackPanel.ChildrenTransitions> <!--<StackPanel.ChildrenTransitions>
<TransitionCollection> <TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True" /> <PopupThemeTransition />
-->
<!--<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True" />-->
<!--
</TransitionCollection> </TransitionCollection>
</StackPanel.ChildrenTransitions> </StackPanel.ChildrenTransitions>-->
<!-- Hero Banner - Windows 11 style --> <!-- Hero Banner - Windows 11 style -->
<Grid <Grid
@@ -46,9 +49,7 @@
Margin="20,0,0,0" Margin="20,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Spacing="4"> Spacing="4">
<TextBlock <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="Wino Mail" />
Style="{StaticResource TitleTextBlockStyle}"
Text="Wino Mail" />
<TextBlock <TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource BodyTextBlockStyle}" Style="{StaticResource BodyTextBlockStyle}"
@@ -62,12 +63,8 @@
Margin="0,8,0,0" Margin="0,8,0,0"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="8"> Spacing="8">
<HyperlinkButton <HyperlinkButton Content="{x:Bind ViewModel.WebsiteUrl, Mode=OneWay}" NavigateUri="{x:Bind ViewModel.WebsiteUrl, Mode=OneWay}" />
Content="{x:Bind ViewModel.WebsiteUrl, Mode=OneWay}" <HyperlinkButton Content="{x:Bind domain:Translator.SettingsPaypal_Title}" NavigateUri="{x:Bind ViewModel.PaypalUrl, Mode=OneWay}" />
NavigateUri="{x:Bind ViewModel.WebsiteUrl, Mode=OneWay}" />
<HyperlinkButton
Content="{x:Bind domain:Translator.SettingsPaypal_Title}"
NavigateUri="{x:Bind ViewModel.PaypalUrl, Mode=OneWay}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
@@ -194,6 +191,17 @@
</controls:SettingsCard.HeaderIcon> </controls:SettingsCard.HeaderIcon>
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard
Click="SettingOptionClicked"
Description="{x:Bind domain:Translator.SettingsEmailTemplates_Description}"
Header="{x:Bind domain:Translator.SettingsEmailTemplates_Title}"
IsClickEnabled="True"
Tag="{x:Bind enums:WinoPage.EmailTemplatesPage}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" Glyph="&#xE70F;" />
</controls:SettingsCard.HeaderIcon>
</controls:SettingsCard>
<controls:SettingsCard <controls:SettingsCard
Click="SettingOptionClicked" Click="SettingOptionClicked"
Description="{x:Bind domain:Translator.SettingsStorage_Description}" Description="{x:Bind domain:Translator.SettingsStorage_Description}"
@@ -0,0 +1,66 @@
<abstract:CreateEmailTemplatePageAbstract
x:Class="Wino.Views.Settings.CreateEmailTemplatePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Views.Abstract"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain"
xmlns:helpers="using:Wino.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mailControls="using:Wino.Mail.Controls"
mc:Ignorable="d">
<Grid RowSpacing="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:SettingsCard Grid.Row="0" Header="{x:Bind domain:Translator.SettingsEmailTemplates_NameTitle}" IsClickEnabled="False">
<TextBox
x:Name="TemplateNameTextBox"
PlaceholderText="{x:Bind domain:Translator.SettingsEmailTemplates_NamePlaceholder}"
Text="{x:Bind ViewModel.TemplateName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</controls:SettingsCard>
<controls:SettingsCard Grid.Row="1" Header="{x:Bind domain:Translator.SettingsEmailTemplates_DescriptionTitle}" IsClickEnabled="False">
<TextBox
AcceptsReturn="True"
PlaceholderText="{x:Bind domain:Translator.SettingsEmailTemplates_DescriptionPlaceholder}"
Text="{x:Bind ViewModel.TemplateDescription, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</controls:SettingsCard>
<StackPanel Grid.Row="2" Spacing="8">
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="{x:Bind domain:Translator.SettingsEmailTemplates_ContentTitle}" />
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind domain:Translator.SettingsEmailTemplates_ContentDescription}" />
<mailControls:EditorTabbedCommandBarControl CommandTarget="{x:Bind WebViewEditor}" />
<Border
MinHeight="420"
Background="{ThemeResource WinoContentZoneBackgroud}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="7">
<mailControls:WebViewEditorControl x:Name="WebViewEditor" />
</Border>
</StackPanel>
<StackPanel
Grid.Row="3"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="8">
<Button
Click="DeleteClicked"
Content="{x:Bind domain:Translator.Buttons_Delete}"
Style="{StaticResource AccentButtonStyle}"
Visibility="{x:Bind helpers:XamlHelpers.BoolToVisibilityConverter(ViewModel.IsExistingTemplate), Mode=OneWay}" />
<Button
Click="SaveClicked"
Content="{x:Bind domain:Translator.Buttons_Save}"
Style="{StaticResource AccentButtonStyle}" />
</StackPanel>
</Grid>
</abstract:CreateEmailTemplatePageAbstract>
@@ -0,0 +1,42 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Navigation;
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class CreateEmailTemplatePage : CreateEmailTemplatePageAbstract
{
public CreateEmailTemplatePage()
{
InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var htmlContent = await ViewModel.LoadAsync(e.Parameter);
await WebViewEditor.RenderHtmlAsync(htmlContent);
if (!ViewModel.IsExistingTemplate)
{
TemplateNameTextBox.Focus(FocusState.Programmatic);
}
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
WebViewEditor.Dispose();
}
private async void SaveClicked(object sender, RoutedEventArgs e)
{
await ViewModel.SaveAsync(await WebViewEditor.GetHtmlBodyAsync() ?? string.Empty);
}
private async void DeleteClicked(object sender, RoutedEventArgs e)
{
await ViewModel.DeleteAsync();
}
}
@@ -0,0 +1,44 @@
<abstract:EmailTemplatesPageAbstract
x:Class="Wino.Views.Settings.EmailTemplatesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Views.Abstract"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain"
xmlns:entities="using:Wino.Core.Domain.Entities.Mail"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ScrollViewer>
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard
Click="NewTemplateClicked"
Description="{x:Bind domain:Translator.SettingsEmailTemplates_NewTemplateDescription}"
Header="{x:Bind domain:Translator.SettingsEmailTemplates_NewTemplateTitle}"
IsClickEnabled="True">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE710;" />
</controls:SettingsCard.HeaderIcon>
</controls:SettingsCard>
<ItemsControl ItemsSource="{x:Bind ViewModel.EmailTemplates, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="entities:EmailTemplate">
<controls:SettingsCard
Click="TemplateClicked"
Description="{x:Bind Description}"
Header="{x:Bind Name}"
IsClickEnabled="True"
Margin="0,0,0,4"
Tag="{x:Bind}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE70F;" />
</controls:SettingsCard.HeaderIcon>
</controls:SettingsCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</abstract:EmailTemplatesPageAbstract>
@@ -0,0 +1,33 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Navigation;
using Wino.Core.Domain.Entities.Mail;
using Wino.Views.Abstract;
namespace Wino.Views.Settings;
public sealed partial class EmailTemplatesPage : EmailTemplatesPageAbstract
{
public EmailTemplatesPage()
{
InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await ViewModel.LoadAsync();
}
private void NewTemplateClicked(object sender, RoutedEventArgs e)
{
ViewModel.CreateTemplate();
}
private void TemplateClicked(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement element && element.Tag is EmailTemplate template)
{
ViewModel.OpenTemplate(template);
}
}
}
@@ -50,6 +50,9 @@ public sealed partial class SettingsPage : SettingsPageAbstract,
case WinoPage.StoragePage: case WinoPage.StoragePage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsStorage_Title, WinoPage.StoragePage)); WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsStorage_Title, WinoPage.StoragePage));
break; break;
case WinoPage.EmailTemplatesPage:
WeakReferenceMessenger.Default.Send(new BreadcrumbNavigationRequested(Translator.SettingsEmailTemplates_Title, WinoPage.EmailTemplatesPage));
break;
} }
} }
+6
View File
@@ -375,6 +375,12 @@
<Page Update="Views\Settings\AppPreferencesPage.xaml"> <Page Update="Views\Settings\AppPreferencesPage.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Update="Views\Settings\CreateEmailTemplatePage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Views\Settings\EmailTemplatesPage.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Views\Settings\EditAccountDetailsPage.xaml"> <Page Update="Views\Settings\EditAccountDetailsPage.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
+2
View File
@@ -56,6 +56,7 @@ public class DatabaseService : IDatabaseService
Connection.CreateTableAsync<ContactGroupMember>(), Connection.CreateTableAsync<ContactGroupMember>(),
Connection.CreateTableAsync<CustomServerInformation>(), Connection.CreateTableAsync<CustomServerInformation>(),
Connection.CreateTableAsync<AccountSignature>(), Connection.CreateTableAsync<AccountSignature>(),
Connection.CreateTableAsync<EmailTemplate>(),
Connection.CreateTableAsync<MergedInbox>(), Connection.CreateTableAsync<MergedInbox>(),
Connection.CreateTableAsync<MailAccountPreferences>(), Connection.CreateTableAsync<MailAccountPreferences>(),
Connection.CreateTableAsync<MailAccountAlias>(), Connection.CreateTableAsync<MailAccountAlias>(),
@@ -200,6 +201,7 @@ SET {nameof(KeyboardShortcut.Action)} =
await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_MailAccount_Order ON MailAccount([Order])").ConfigureAwait(false); await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_MailAccount_Order ON MailAccount([Order])").ConfigureAwait(false);
await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_AccountSignature_MailAccountId ON AccountSignature(MailAccountId)").ConfigureAwait(false); await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_AccountSignature_MailAccountId ON AccountSignature(MailAccountId)").ConfigureAwait(false);
await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_EmailTemplate_Name ON EmailTemplate(Name)").ConfigureAwait(false);
await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_MailAccountAlias_AccountId ON MailAccountAlias(AccountId)").ConfigureAwait(false); await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_MailAccountAlias_AccountId ON MailAccountAlias(AccountId)").ConfigureAwait(false);
await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_MailAccountAlias_AccountId_AliasAddress ON MailAccountAlias(AccountId, AliasAddress)").ConfigureAwait(false); await Connection.ExecuteAsync("CREATE INDEX IF NOT EXISTS IX_MailAccountAlias_AccountId_AliasAddress ON MailAccountAlias(AccountId, AliasAddress)").ConfigureAwait(false);
await Connection.ExecuteAsync("CREATE UNIQUE INDEX IF NOT EXISTS IX_MailAccountPreferences_AccountId ON MailAccountPreferences(AccountId)").ConfigureAwait(false); await Connection.ExecuteAsync("CREATE UNIQUE INDEX IF NOT EXISTS IX_MailAccountPreferences_AccountId ON MailAccountPreferences(AccountId)").ConfigureAwait(false);
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Interfaces;
namespace Wino.Services;
public class EmailTemplateService(IDatabaseService databaseService) : BaseDatabaseService(databaseService), IEmailTemplateService
{
public Task<List<EmailTemplate>> GetEmailTemplatesAsync()
{
return Connection.Table<EmailTemplate>()
.OrderBy(t => t.Name)
.ToListAsync();
}
public Task<EmailTemplate> GetEmailTemplateAsync(Guid templateId)
{
return Connection.Table<EmailTemplate>()
.FirstOrDefaultAsync(t => t.Id == templateId);
}
public async Task<EmailTemplate> CreateEmailTemplateAsync(EmailTemplate template)
{
await Connection.InsertAsync(template, typeof(EmailTemplate)).ConfigureAwait(false);
return template;
}
public async Task<EmailTemplate> UpdateEmailTemplateAsync(EmailTemplate template)
{
await Connection.UpdateAsync(template, typeof(EmailTemplate)).ConfigureAwait(false);
return template;
}
public async Task<EmailTemplate> DeleteEmailTemplateAsync(EmailTemplate template)
{
await Connection.DeleteAsync<EmailTemplate>(template.Id).ConfigureAwait(false);
return template;
}
}
+1
View File
@@ -23,6 +23,7 @@ public static class ServicesContainerSetup
services.AddTransient<IAccountService, AccountService>(); services.AddTransient<IAccountService, AccountService>();
services.AddTransient<IContactService, ContactService>(); services.AddTransient<IContactService, ContactService>();
services.AddTransient<ISignatureService, SignatureService>(); services.AddTransient<ISignatureService, SignatureService>();
services.AddTransient<IEmailTemplateService, EmailTemplateService>();
services.AddTransient<IContextMenuItemService, ContextMenuItemService>(); services.AddTransient<IContextMenuItemService, ContextMenuItemService>();
services.AddTransient<ISpecialImapProviderConfigResolver, SpecialImapProviderConfigResolver>(); services.AddTransient<ISpecialImapProviderConfigResolver, SpecialImapProviderConfigResolver>();
services.AddTransient<IKeyboardShortcutService, KeyboardShortcutService>(); services.AddTransient<IKeyboardShortcutService, KeyboardShortcutService>();