Event compose implementation.

This commit is contained in:
Burak Kaan Köse
2026-03-07 01:46:07 +01:00
parent 6608baed69
commit e94cce451f
26 changed files with 1285 additions and 674 deletions
@@ -35,9 +35,6 @@ public partial class CalendarAccountSettingsPageViewModel : CalendarBaseViewMode
[ObservableProperty] [ObservableProperty]
public partial bool IsSyncEnabled { get; set; } public partial bool IsSyncEnabled { get; set; }
[ObservableProperty]
public partial bool IsPrimaryCalendar { get; set; }
public ObservableCollection<ShowAsOption> ShowAsOptions { get; } = new ObservableCollection<ShowAsOption>(); public ObservableCollection<ShowAsOption> ShowAsOptions { get; } = new ObservableCollection<ShowAsOption>();
[ObservableProperty] [ObservableProperty]
@@ -82,7 +79,6 @@ public partial class CalendarAccountSettingsPageViewModel : CalendarBaseViewMode
// Initialize properties from AccountCalendar // Initialize properties from AccountCalendar
AccountColorHex = AccountCalendar.BackgroundColorHex ?? "#0078D4"; AccountColorHex = AccountCalendar.BackgroundColorHex ?? "#0078D4";
IsSyncEnabled = AccountCalendar.IsSynchronizationEnabled; IsSyncEnabled = AccountCalendar.IsSynchronizationEnabled;
IsPrimaryCalendar = AccountCalendar.IsPrimary;
SelectedDefaultShowAsOption = ShowAsOptions.FirstOrDefault(o => o.ShowAs == AccountCalendar.DefaultShowAs) ?? ShowAsOptions[2]; SelectedDefaultShowAsOption = ShowAsOptions.FirstOrDefault(o => o.ShowAs == AccountCalendar.DefaultShowAs) ?? ShowAsOptions[2];
} }
@@ -104,15 +100,6 @@ public partial class CalendarAccountSettingsPageViewModel : CalendarBaseViewMode
} }
} }
partial void OnIsPrimaryCalendarChanged(bool value)
{
if (AccountCalendar != null)
{
AccountCalendar.IsPrimary = value;
SaveChangesAsync();
}
}
partial void OnSelectedDefaultShowAsOptionChanged(ShowAsOption value) partial void OnSelectedDefaultShowAsOptionChanged(ShowAsOption value)
{ {
if (AccountCalendar != null && value != null) if (AccountCalendar != null && value != null)
@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Serilog; using Serilog;
using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Calendar.ViewModels.Interfaces; using Wino.Calendar.ViewModels.Interfaces;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Collections; using Wino.Core.Domain.Collections;
@@ -122,12 +123,14 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
if (mode == NavigationMode.Back) if (mode == NavigationMode.Back)
{ {
await InitializeAccountCalendarsAsync(); await InitializeAccountCalendarsAsync();
ValidateConfiguredNewEventCalendar();
return; return;
} }
UpdateDateNavigationHeaderItems(); UpdateDateNavigationHeaderItems();
await InitializeAccountCalendarsAsync(); await InitializeAccountCalendarsAsync();
ValidateConfiguredNewEventCalendar();
await ShowWhatIsNewIfNeededAsync(); await ShowWhatIsNewIfNeededAsync();
@@ -310,6 +313,10 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
[RelayCommand] [RelayCommand]
private async Task NewEventAsync() private async Task NewEventAsync()
{
var pickedCalendar = TryResolveConfiguredNewEventCalendar();
if (pickedCalendar == null)
{ {
var availableGroups = AccountCalendarStateService.GroupedAccountCalendars var availableGroups = AccountCalendarStateService.GroupedAccountCalendars
.Where(group => group.AccountCalendars.Count > 0) .Where(group => group.AccountCalendars.Count > 0)
@@ -329,7 +336,9 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
return; return;
} }
var pickedCalendar = await _dialogService.ShowSingleCalendarPickerDialogAsync(availableGroups); pickedCalendar = await _dialogService.ShowSingleCalendarPickerDialogAsync(availableGroups);
}
if (pickedCalendar == null) if (pickedCalendar == null)
return; return;
@@ -472,7 +481,43 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
} }
public async void Receive(AccountRemovedMessage message) public async void Receive(AccountRemovedMessage message)
=> await InitializeAccountCalendarsAsync(); {
await InitializeAccountCalendarsAsync();
ValidateConfiguredNewEventCalendar();
}
private AccountCalendar TryResolveConfiguredNewEventCalendar()
{
ValidateConfiguredNewEventCalendar();
if (PreferencesService.NewEventButtonBehavior != NewEventButtonBehavior.AlwaysUseSpecificCalendar
|| !PreferencesService.DefaultNewEventCalendarId.HasValue)
{
return null;
}
return AccountCalendarStateService.AllCalendars
.FirstOrDefault(calendar => calendar.Id == PreferencesService.DefaultNewEventCalendarId.Value)?
.AccountCalendar;
}
private void ValidateConfiguredNewEventCalendar()
{
if (PreferencesService.NewEventButtonBehavior != NewEventButtonBehavior.AlwaysUseSpecificCalendar
|| !PreferencesService.DefaultNewEventCalendarId.HasValue)
{
return;
}
var exists = AccountCalendarStateService.AllCalendars
.Any(calendar => calendar.Id == PreferencesService.DefaultNewEventCalendarId.Value);
if (exists)
return;
PreferencesService.NewEventButtonBehavior = NewEventButtonBehavior.AskEachTime;
PreferencesService.DefaultNewEventCalendarId = null;
}
private static (DateTime StartDate, DateTime EndDate) GetDefaultComposeDateRange() private static (DateTime StartDate, DateTime EndDate) GetDefaultComposeDateRange()
{ {
@@ -6,18 +6,20 @@ using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Mail;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using EmailValidation;
using Wino.Calendar.ViewModels.Data; using Wino.Calendar.ViewModels.Data;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Models.Navigation; using Wino.Core.Domain.Models.Navigation;
using Wino.Core.Domain.Validation;
using Wino.Core.ViewModels; using Wino.Core.ViewModels;
namespace Wino.Calendar.ViewModels; namespace Wino.Calendar.ViewModels;
@@ -31,10 +33,12 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
private readonly IContactService _contactService; private readonly IContactService _contactService;
private readonly IPreferencesService _preferencesService; private readonly IPreferencesService _preferencesService;
private readonly IUnderlyingThemeService _underlyingThemeService; private readonly IUnderlyingThemeService _underlyingThemeService;
private readonly CalendarEventComposeResultValidator _composeResultValidator = new();
public Func<Task<string>> GetHtmlNotesAsync { get; set; } public Func<Task<string>> GetHtmlNotesAsync { get; set; }
public ObservableCollection<AccountCalendarViewModel> AvailableCalendars { get; } = []; public ObservableCollection<AccountCalendarViewModel> AvailableCalendars { get; } = [];
public ObservableCollection<GroupedAccountCalendarViewModel> AvailableCalendarGroups { get; } = [];
public ObservableCollection<CalendarComposeAttendeeViewModel> Attendees { get; } = []; public ObservableCollection<CalendarComposeAttendeeViewModel> Attendees { get; } = [];
public ObservableCollection<CalendarComposeAttachmentViewModel> Attachments { get; } = []; public ObservableCollection<CalendarComposeAttachmentViewModel> Attachments { get; } = [];
public ObservableCollection<ShowAsOption> ShowAsOptions { get; } = []; public ObservableCollection<ShowAsOption> ShowAsOptions { get; } = [];
@@ -97,6 +101,8 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
public CalendarSettings CurrentSettings { get; } public CalendarSettings CurrentSettings { get; }
public string TimePickerClockIdentifier => CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "24HourClock" : "12HourClock"; public string TimePickerClockIdentifier => CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "24HourClock" : "12HourClock";
public bool HasAttachments => Attachments.Count > 0; public bool HasAttachments => Attachments.Count > 0;
public string SelectedCalendarDisplayText => SelectedCalendar?.Name ?? Translator.CalendarEventCompose_SelectCalendar;
public string SelectedCalendarAccountText => SelectedCalendar?.Account?.Address ?? string.Empty;
public CalendarEventComposePageViewModel(IAccountService accountService, public CalendarEventComposePageViewModel(IAccountService accountService,
ICalendarService calendarService, ICalendarService calendarService,
@@ -169,11 +175,13 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
partial void OnSelectedCalendarChanged(AccountCalendarViewModel value) partial void OnSelectedCalendarChanged(AccountCalendarViewModel value)
{ {
if (value == null || SelectedShowAsOption != null) if (value == null)
return; return;
SelectedShowAsOption = ShowAsOptions.FirstOrDefault(option => option.ShowAs == value.DefaultShowAs) SelectedShowAsOption = ShowAsOptions.FirstOrDefault(option => option.ShowAs == value.DefaultShowAs)
?? ShowAsOptions.FirstOrDefault(); ?? ShowAsOptions.FirstOrDefault();
OnPropertyChanged(nameof(SelectedCalendarDisplayText));
OnPropertyChanged(nameof(SelectedCalendarAccountText));
} }
partial void OnIsAllDayChanged(bool value) partial void OnIsAllDayChanged(bool value)
@@ -263,36 +271,24 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
[RelayCommand] [RelayCommand]
private async Task CreateAsync() private async Task CreateAsync()
{ {
if (!await ValidateAsync())
return;
var uniqueAttendees = Attendees var uniqueAttendees = Attendees
.GroupBy(attendee => attendee.Email, StringComparer.OrdinalIgnoreCase) .GroupBy(attendee => attendee.Email, StringComparer.OrdinalIgnoreCase)
.Select(group => group.First()) .Select(group => group.First())
.ToList(); .ToList();
var htmlNotes = GetHtmlNotesAsync == null ? string.Empty : await GetHtmlNotesAsync(); var createdResult = await BuildResultAsync(uniqueAttendees);
var effectiveStart = GetEffectiveStartDateTime();
var effectiveEnd = GetEffectiveEndDateTime();
LastCreatedResult = new CalendarEventComposeResult try
{ {
CalendarId = SelectedCalendar!.Id, _composeResultValidator.Validate(createdResult);
AccountId = SelectedCalendar.Account.Id, }
Title = Title.Trim(), catch (CalendarEventComposeValidationException ex)
Location = Location?.Trim() ?? string.Empty, {
HtmlNotes = htmlNotes, ShowValidationMessage(ex.Message);
StartDate = effectiveStart, return;
EndDate = effectiveEnd, }
IsAllDay = IsAllDay,
TimeZoneId = TimeZoneInfo.Local.Id, LastCreatedResult = createdResult;
ShowAs = SelectedShowAsOption?.ShowAs ?? SelectedCalendar.DefaultShowAs,
SelectedReminders = BuildSelectedReminders(),
Attendees = BuildAttendees(uniqueAttendees),
Attachments = Attachments.Select(attachment => attachment.ToDraftModel()).ToList(),
Recurrence = BuildRecurrenceRule(),
RecurrenceSummary = RecurrenceSummary
};
_navigationService.GoBack(); _navigationService.GoBack();
} }
@@ -307,7 +303,7 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
public async Task<CalendarComposeAttendeeViewModel> GetAttendeeAsync(string tokenText) public async Task<CalendarComposeAttendeeViewModel> GetAttendeeAsync(string tokenText)
{ {
if (!IsValidEmailAddress(tokenText)) if (!EmailValidator.Validate(tokenText))
return null; return null;
var existing = Attendees.Any(attendee => attendee.Email.Equals(tokenText, StringComparison.OrdinalIgnoreCase)); var existing = Attendees.Any(attendee => attendee.Email.Equals(tokenText, StringComparison.OrdinalIgnoreCase));
@@ -359,26 +355,38 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
private async Task LoadAvailableCalendarsAsync() private async Task LoadAvailableCalendarsAsync()
{ {
var accountCalendars = new List<AccountCalendarViewModel>(); var accountCalendars = new List<AccountCalendarViewModel>();
var groupedCalendars = new List<GroupedAccountCalendarViewModel>();
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false); var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
foreach (var account in accounts) foreach (var account in accounts)
{ {
var calendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false); var calendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
var viewModels = calendars
.Select(calendar => new AccountCalendarViewModel(account, calendar))
.ToList();
foreach (var calendar in calendars) accountCalendars.AddRange(viewModels);
if (viewModels.Count > 0)
{ {
accountCalendars.Add(new AccountCalendarViewModel(account, calendar)); groupedCalendars.Add(new GroupedAccountCalendarViewModel(account, viewModels));
} }
} }
await ExecuteUIThread(() => await ExecuteUIThread(() =>
{ {
AvailableCalendars.Clear(); AvailableCalendars.Clear();
AvailableCalendarGroups.Clear();
foreach (var calendar in accountCalendars.OrderBy(calendar => calendar.Account.Name).ThenBy(calendar => calendar.Name)) foreach (var calendar in accountCalendars.OrderBy(calendar => calendar.Account.Name).ThenBy(calendar => calendar.Name))
{ {
AvailableCalendars.Add(calendar); AvailableCalendars.Add(calendar);
} }
foreach (var group in groupedCalendars.OrderBy(group => group.Account.Name))
{
AvailableCalendarGroups.Add(group);
}
}); });
} }
@@ -424,68 +432,35 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
AllDayEndDate = new DateTimeOffset((isAllDay ? endDate.Date : startDate.Date.AddDays(1))); AllDayEndDate = new DateTimeOffset((isAllDay ? endDate.Date : startDate.Date.AddDays(1)));
} }
private async Task<bool> ValidateAsync() private async Task<CalendarEventComposeResult> BuildResultAsync(List<CalendarComposeAttendeeViewModel> uniqueAttendees)
{ {
if (SelectedCalendar == null)
{
ShowValidationMessage(Translator.CalendarEventCompose_ValidationMissingCalendar);
return false;
}
if (string.IsNullOrWhiteSpace(Title))
{
ShowValidationMessage(Translator.CalendarEventCompose_ValidationMissingTitle);
return false;
}
if (IsAllDay)
{
if (AllDayEndDate.Date <= StartDate.Date)
{
ShowValidationMessage(Translator.CalendarEventCompose_ValidationInvalidAllDayRange);
return false;
}
}
else if (GetEffectiveEndDateTime() <= GetEffectiveStartDateTime())
{
ShowValidationMessage(Translator.CalendarEventCompose_ValidationInvalidTimeRange);
return false;
}
if (RecurrenceEndDate.HasValue && RecurrenceEndDate.Value.Date < StartDate.Date) if (RecurrenceEndDate.HasValue && RecurrenceEndDate.Value.Date < StartDate.Date)
{ {
ShowValidationMessage(Translator.CalendarEventCompose_ValidationInvalidRecurrenceEnd); throw new CalendarEventComposeValidationException(Translator.CalendarEventCompose_ValidationInvalidRecurrenceEnd);
return false;
} }
var missingAttachments = Attachments var htmlNotes = GetHtmlNotesAsync == null ? string.Empty : await GetHtmlNotesAsync();
.Where(attachment => !File.Exists(attachment.FilePath)) var effectiveStart = GetEffectiveStartDateTime();
.Select(attachment => attachment.FileName) var effectiveEnd = GetEffectiveEndDateTime();
.ToList();
if (missingAttachments.Count > 0) return new CalendarEventComposeResult
{ {
ShowValidationMessage(string.Format(Translator.CalendarEventCompose_ValidationMissingAttachment, string.Join(", ", missingAttachments))); CalendarId = SelectedCalendar?.Id ?? Guid.Empty,
return false; AccountId = SelectedCalendar?.Account.Id ?? Guid.Empty,
} Title = Title.Trim(),
Location = Location?.Trim() ?? string.Empty,
var normalizedAttendees = Attendees HtmlNotes = htmlNotes,
.Where(attendee => !string.IsNullOrWhiteSpace(attendee.Email)) StartDate = effectiveStart,
.Select(attendee => attendee.Email.Trim()) EndDate = effectiveEnd,
.ToList(); IsAllDay = IsAllDay,
TimeZoneId = TimeZoneInfo.Local.Id,
if (normalizedAttendees.Any(address => !IsValidEmailAddress(address))) ShowAs = SelectedShowAsOption?.ShowAs ?? SelectedCalendar?.DefaultShowAs ?? CalendarItemShowAs.Busy,
{ SelectedReminders = BuildSelectedReminders(),
ShowValidationMessage(Translator.CalendarEventCompose_ValidationInvalidAttendee); Attendees = BuildAttendees(uniqueAttendees),
return false; Attachments = Attachments.Select(attachment => attachment.ToDraftModel()).ToList(),
} Recurrence = BuildRecurrenceRule(),
RecurrenceSummary = RecurrenceSummary
if (GetHtmlNotesAsync != null) };
{
await GetHtmlNotesAsync();
}
return true;
} }
private List<Reminder> BuildSelectedReminders() private List<Reminder> BuildSelectedReminders()
@@ -676,18 +651,6 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
OnPropertyChanged(nameof(HasAttachments)); OnPropertyChanged(nameof(HasAttachments));
} }
private static bool IsValidEmailAddress(string address)
{
try
{
var parsedAddress = new MailAddress(address);
return parsedAddress.Address.Equals(address, StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
} }
public partial class CalendarComposeFrequencyOption : ObservableObject public partial class CalendarComposeFrequencyOption : ObservableObject
@@ -1,20 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using Wino.Calendar.ViewModels.Data;
using CommunityToolkit.Mvvm.Messaging;
using Wino.Core.Domain; using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Translations; using Wino.Core.Domain.Translations;
using Wino.Core.ViewModels; using Wino.Core.ViewModels;
using Wino.Messaging.Client.Calendar;
using Wino.Messaging.Client.Navigation;
namespace Wino.Calendar.ViewModels; namespace Wino.Calendar.ViewModels;
@@ -56,12 +53,21 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
[ObservableProperty] [ObservableProperty]
public partial int SelectedDefaultSnoozeIndex { get; set; } public partial int SelectedDefaultSnoozeIndex { get; set; }
public ObservableCollection<MailAccount> Accounts { get; } = [];
public ObservableCollection<CalendarNewEventBehaviorOption> NewEventBehaviorOptions { get; } = [];
public ObservableCollection<AccountCalendarViewModel> AvailableNewEventCalendars { get; } = [];
[ObservableProperty]
public partial CalendarNewEventBehaviorOption SelectedNewEventBehaviorOption { get; set; }
[ObservableProperty]
public partial AccountCalendarViewModel SelectedNewEventCalendar { get; set; }
public bool ShouldShowSpecificNewEventCalendar => SelectedNewEventBehaviorOption?.Behavior == NewEventButtonBehavior.AlwaysUseSpecificCalendar;
public IPreferencesService PreferencesService { get; } public IPreferencesService PreferencesService { get; }
private readonly ICalendarService _calendarService; private readonly ICalendarService _calendarService;
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
public ObservableCollection<MailAccount> Accounts { get; } = new ObservableCollection<MailAccount>();
private readonly bool _isLoaded = false; private readonly bool _isLoaded = false;
public CalendarSettingsPageViewModel(IPreferencesService preferencesService, ICalendarService calendarService, IAccountService accountService) public CalendarSettingsPageViewModel(IPreferencesService preferencesService, ICalendarService calendarService, IAccountService accountService)
@@ -71,10 +77,8 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
_accountService = accountService; _accountService = accountService;
var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage); var currentLanguageLanguageCode = WinoTranslationDictionary.GetLanguageFileNameRelativePath(preferencesService.CurrentLanguage);
var cultureInfo = new CultureInfo(currentLanguageLanguageCode); var cultureInfo = new CultureInfo(currentLanguageLanguageCode);
// Populate the day names list
for (var i = 0; i < 7; i++) for (var i = 0; i < 7; i++)
{ {
DayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]); DayNames.Add(cultureInfo.DateTimeFormat.DayNames[i]);
@@ -89,7 +93,6 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
WorkingDayStartIndex = DayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart)); WorkingDayStartIndex = DayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayStart));
WorkingDayEndIndex = DayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd)); WorkingDayEndIndex = DayNames.IndexOf(cultureInfo.DateTimeFormat.GetDayName(preferencesService.WorkingDayEnd));
// Initialize reminder options
var predefinedMinutes = _calendarService.GetPredefinedReminderMinutes(); var predefinedMinutes = _calendarService.GetPredefinedReminderMinutes();
ReminderOptions.Add("None"); ReminderOptions.Add("None");
foreach (var minutes in predefinedMinutes) foreach (var minutes in predefinedMinutes)
@@ -102,10 +105,9 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
ReminderOptions.Add(displayText); ReminderOptions.Add(displayText);
} }
// Set selected index based on current default reminder setting
if (preferencesService.DefaultReminderDurationInSeconds == 0) if (preferencesService.DefaultReminderDurationInSeconds == 0)
{ {
SelectedDefaultReminderIndex = 0; // None SelectedDefaultReminderIndex = 0;
} }
else else
{ {
@@ -123,35 +125,47 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
var selectedSnoozeIndex = Array.IndexOf(supportedSnoozeMinutes, preferencesService.DefaultSnoozeDurationInMinutes); var selectedSnoozeIndex = Array.IndexOf(supportedSnoozeMinutes, preferencesService.DefaultSnoozeDurationInMinutes);
SelectedDefaultSnoozeIndex = selectedSnoozeIndex >= 0 ? selectedSnoozeIndex : 0; SelectedDefaultSnoozeIndex = selectedSnoozeIndex >= 0 ? selectedSnoozeIndex : 0;
NewEventBehaviorOptions.Add(new CalendarNewEventBehaviorOption(NewEventButtonBehavior.AskEachTime, Translator.CalendarSettings_NewEventBehavior_AskEachTime));
NewEventBehaviorOptions.Add(new CalendarNewEventBehaviorOption(NewEventButtonBehavior.AlwaysUseSpecificCalendar, Translator.CalendarSettings_NewEventBehavior_AlwaysUseSpecificCalendar));
SelectedNewEventBehaviorOption = NewEventBehaviorOptions.FirstOrDefault(option => option.Behavior == preferencesService.NewEventButtonBehavior)
?? NewEventBehaviorOptions.First();
_isLoaded = true; _isLoaded = true;
// Load accounts with calendar support
LoadAccountsAsync(); LoadAccountsAsync();
} }
private async void LoadAccountsAsync() private async void LoadAccountsAsync()
{ {
var accounts = await _accountService.GetAccountsAsync(); var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
var calendarsByAccount = new List<(MailAccount Account, List<AccountCalendarViewModel> Calendars)>();
foreach (var account in accounts)
{
var calendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
calendarsByAccount.Add((account, calendars.Select(calendar => new AccountCalendarViewModel(account, calendar)).ToList()));
}
await Dispatcher.ExecuteOnUIThread(() => await Dispatcher.ExecuteOnUIThread(() =>
{ {
Accounts.Clear(); Accounts.Clear();
AvailableNewEventCalendars.Clear();
foreach (var account in accounts) foreach (var account in accounts)
{ {
Accounts.Add(account); Accounts.Add(account);
} }
});
foreach (var accountCalendars in calendarsByAccount)
{
foreach (var calendar in accountCalendars.Calendars)
{
AvailableNewEventCalendars.Add(calendar);
}
} }
[RelayCommand] ApplyStoredNewEventCalendarPreference();
private void NavigateToAccountSettings(MailAccount account) });
{
if (account == null) return;
Messenger.Send(new BreadcrumbNavigationRequested(
string.Format(Translator.CalendarAccountSettings_Description, account.Name),
WinoPage.CalendarAccountSettingsPage,
account.Id));
} }
partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings(); partial void OnCellHourHeightChanged(double oldValue, double newValue) => SaveSettings();
@@ -163,10 +177,17 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings(); partial void OnWorkingDayEndIndexChanged(int value) => SaveSettings();
partial void OnSelectedDefaultReminderIndexChanged(int value) => SaveSettings(); partial void OnSelectedDefaultReminderIndexChanged(int value) => SaveSettings();
partial void OnSelectedDefaultSnoozeIndexChanged(int value) => SaveSettings(); partial void OnSelectedDefaultSnoozeIndexChanged(int value) => SaveSettings();
partial void OnSelectedNewEventBehaviorOptionChanged(CalendarNewEventBehaviorOption value)
{
OnPropertyChanged(nameof(ShouldShowSpecificNewEventCalendar));
SaveSettings();
}
partial void OnSelectedNewEventCalendarChanged(AccountCalendarViewModel value) => SaveSettings();
public void SaveSettings() public void SaveSettings()
{ {
if (!_isLoaded) return; if (!_isLoaded)
return;
PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch PreferencesService.FirstDayOfWeek = SelectedFirstDayOfWeekIndex switch
{ {
@@ -209,10 +230,9 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
PreferencesService.WorkingHourEnd = WorkingHourEnd; PreferencesService.WorkingHourEnd = WorkingHourEnd;
PreferencesService.HourHeight = CellHourHeight; PreferencesService.HourHeight = CellHourHeight;
// Save default reminder setting
if (SelectedDefaultReminderIndex == 0) if (SelectedDefaultReminderIndex == 0)
{ {
PreferencesService.DefaultReminderDurationInSeconds = 0; // None PreferencesService.DefaultReminderDurationInSeconds = 0;
} }
else else
{ {
@@ -228,6 +248,47 @@ public partial class CalendarSettingsPageViewModel : CalendarBaseViewModel
PreferencesService.DefaultSnoozeDurationInMinutes = supportedSnoozeMinutes[selectedIndex]; PreferencesService.DefaultSnoozeDurationInMinutes = supportedSnoozeMinutes[selectedIndex];
} }
Messenger.Send(new CalendarSettingsUpdatedMessage()); var newEventBehavior = SelectedNewEventBehaviorOption?.Behavior ?? NewEventButtonBehavior.AskEachTime;
if (newEventBehavior == NewEventButtonBehavior.AlwaysUseSpecificCalendar && SelectedNewEventCalendar != null)
{
PreferencesService.NewEventButtonBehavior = NewEventButtonBehavior.AlwaysUseSpecificCalendar;
PreferencesService.DefaultNewEventCalendarId = SelectedNewEventCalendar.Id;
}
else
{
PreferencesService.NewEventButtonBehavior = NewEventButtonBehavior.AskEachTime;
PreferencesService.DefaultNewEventCalendarId = null;
}
}
private void ApplyStoredNewEventCalendarPreference()
{
var configuredCalendarId = PreferencesService.DefaultNewEventCalendarId;
var configuredCalendar = configuredCalendarId.HasValue
? AvailableNewEventCalendars.FirstOrDefault(calendar => calendar.Id == configuredCalendarId.Value)
: null;
if (PreferencesService.NewEventButtonBehavior == NewEventButtonBehavior.AlwaysUseSpecificCalendar && configuredCalendar == null)
{
SelectedNewEventBehaviorOption = NewEventBehaviorOptions.First(option => option.Behavior == NewEventButtonBehavior.AskEachTime);
SelectedNewEventCalendar = null;
return;
}
SelectedNewEventCalendar = configuredCalendar
?? AvailableNewEventCalendars.FirstOrDefault(calendar => calendar.IsPrimary)
?? AvailableNewEventCalendars.FirstOrDefault();
}
}
public sealed class CalendarNewEventBehaviorOption
{
public NewEventButtonBehavior Behavior { get; }
public string DisplayText { get; }
public CalendarNewEventBehaviorOption(NewEventButtonBehavior behavior, string displayText)
{
Behavior = behavior;
DisplayText = displayText;
} }
} }
@@ -11,6 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="TimePeriodLibrary.NET" /> <PackageReference Include="TimePeriodLibrary.NET" />
<PackageReference Include="EmailValidation" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Enums;
public enum NewEventButtonBehavior
{
AskEachTime = 0,
AlwaysUseSpecificCalendar = 1
}
@@ -0,0 +1,10 @@
using System;
namespace Wino.Core.Domain.Exceptions;
public sealed class CalendarEventComposeValidationException : Exception
{
public CalendarEventComposeValidationException(string message) : base(message)
{
}
}
@@ -18,6 +18,7 @@ public interface ICalendarService
Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar); Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar);
Task InsertAccountCalendarAsync(AccountCalendar accountCalendar); Task InsertAccountCalendarAsync(AccountCalendar accountCalendar);
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar); Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
Task SetPrimaryCalendarAsync(Guid accountId, Guid accountCalendarId);
Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees); Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
/// <summary> /// <summary>
@@ -227,6 +227,16 @@ public interface IPreferencesService : INotifyPropertyChanged
/// </summary> /// </summary>
int DefaultSnoozeDurationInMinutes { get; set; } int DefaultSnoozeDurationInMinutes { get; set; }
/// <summary>
/// Setting: How the New Event button chooses a calendar.
/// </summary>
NewEventButtonBehavior NewEventButtonBehavior { get; set; }
/// <summary>
/// Setting: Default calendar used when New Event is configured to always use a specific calendar.
/// </summary>
Guid? DefaultNewEventCalendarId { get; set; }
CalendarSettings GetCurrentCalendarSettings(); CalendarSettings GetCurrentCalendarSettings();
#endregion #endregion
@@ -1049,6 +1049,10 @@
"CalendarAccountSettings_DefaultShowAsDescription": "Default availability status for new events created with this account", "CalendarAccountSettings_DefaultShowAsDescription": "Default availability status for new events created with this account",
"CalendarAccountSettings_PrimaryCalendar": "Primary Calendar", "CalendarAccountSettings_PrimaryCalendar": "Primary Calendar",
"CalendarAccountSettings_PrimaryCalendarDescription": "Mark this calendar as the primary calendar for the account", "CalendarAccountSettings_PrimaryCalendarDescription": "Mark this calendar as the primary calendar for the account",
"CalendarSettings_NewEventBehavior_Header": "New Event button behavior",
"CalendarSettings_NewEventBehavior_Description": "Choose whether the New Event button should ask for a calendar each time or always open a specific calendar.",
"CalendarSettings_NewEventBehavior_AskEachTime": "Ask each time.",
"CalendarSettings_NewEventBehavior_AlwaysUseSpecificCalendar": "Always use specific calendar.",
"WhatIsNew_GetStartedButton": "Get Started", "WhatIsNew_GetStartedButton": "Get Started",
"WhatIsNew_ContinueAnywayButton": "Continue anyway", "WhatIsNew_ContinueAnywayButton": "Continue anyway",
"WhatIsNew_PreparingForNewVersionButton": "Preparing for new version...", "WhatIsNew_PreparingForNewVersionButton": "Preparing for new version...",
@@ -0,0 +1,73 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Mail;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Models.Calendar;
namespace Wino.Core.Domain.Validation;
public sealed class CalendarEventComposeResultValidator
{
public void Validate(CalendarEventComposeResult result)
{
ArgumentNullException.ThrowIfNull(result);
if (result.CalendarId == Guid.Empty)
throw new CalendarEventComposeValidationException(Translator.CalendarEventCompose_ValidationMissingCalendar);
if (result.AccountId == Guid.Empty)
throw new CalendarEventComposeValidationException(Translator.CalendarEventCompose_ValidationMissingCalendar);
if (string.IsNullOrWhiteSpace(result.Title))
throw new CalendarEventComposeValidationException(Translator.CalendarEventCompose_ValidationMissingTitle);
if (result.EndDate <= result.StartDate)
{
var message = result.IsAllDay
? Translator.CalendarEventCompose_ValidationInvalidAllDayRange
: Translator.CalendarEventCompose_ValidationInvalidTimeRange;
throw new CalendarEventComposeValidationException(message);
}
var missingAttachments = result.Attachments
.Where(attachment => string.IsNullOrWhiteSpace(attachment.FilePath) || !File.Exists(attachment.FilePath))
.Select(attachment => attachment.FileName)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (missingAttachments.Count > 0)
{
throw new CalendarEventComposeValidationException(
string.Format(Translator.CalendarEventCompose_ValidationMissingAttachment, string.Join(", ", missingAttachments)));
}
var invalidAttendee = result.Attendees
.FirstOrDefault(attendee => string.IsNullOrWhiteSpace(attendee.Email) || !IsValidEmailAddress(attendee.Email.Trim()));
if (invalidAttendee != null)
throw new CalendarEventComposeValidationException(Translator.CalendarEventCompose_ValidationInvalidAttendee);
var duplicateAttendeeGroups = result.Attendees
.Where(attendee => !string.IsNullOrWhiteSpace(attendee.Email))
.GroupBy(attendee => attendee.Email.Trim(), StringComparer.OrdinalIgnoreCase)
.FirstOrDefault(group => group.Count() > 1);
if (duplicateAttendeeGroups != null)
throw new CalendarEventComposeValidationException(Translator.CalendarEventCompose_ValidationInvalidAttendee);
}
private static bool IsValidEmailAddress(string address)
{
try
{
var parsedAddress = new MailAddress(address);
return parsedAddress.Address.Equals(address, StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
}
@@ -0,0 +1,166 @@
using FluentAssertions;
using System.IO;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Models.Calendar;
using Wino.Core.Domain.Validation;
using Xunit;
namespace Wino.Core.Tests.Services;
public sealed class CalendarEventComposeResultValidatorTests
{
private readonly CalendarEventComposeResultValidator _validator = new();
[Fact]
public void Validate_WhenResultIsValid_DoesNotThrow()
{
var tempFilePath = Path.GetTempFileName();
try
{
var result = CreateValidResult();
result.Attachments.Add(new CalendarEventComposeAttachmentDraft
{
Id = Guid.NewGuid(),
FileName = Path.GetFileName(tempFilePath),
FilePath = tempFilePath,
FileExtension = ".tmp",
Size = 12
});
Action act = () => _validator.Validate(result);
act.Should().NotThrow();
}
finally
{
File.Delete(tempFilePath);
}
}
[Fact]
public void Validate_WhenEndDateIsBeforeStartDate_ThrowsValidationException()
{
var result = CreateValidResult();
result.EndDate = result.StartDate.AddMinutes(-30);
Action act = () => _validator.Validate(result);
act.Should()
.Throw<CalendarEventComposeValidationException>()
.WithMessage(Translator.CalendarEventCompose_ValidationInvalidTimeRange);
}
[Fact]
public void Validate_WhenAllDayEndDateMatchesStartDate_ThrowsValidationException()
{
var result = CreateValidResult();
result.IsAllDay = true;
result.EndDate = result.StartDate;
Action act = () => _validator.Validate(result);
act.Should()
.Throw<CalendarEventComposeValidationException>()
.WithMessage(Translator.CalendarEventCompose_ValidationInvalidAllDayRange);
}
[Fact]
public void Validate_WhenAttachmentDoesNotExist_ThrowsValidationException()
{
var result = CreateValidResult();
result.Attachments.Add(new CalendarEventComposeAttachmentDraft
{
Id = Guid.NewGuid(),
FileName = "missing.txt",
FilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.txt"),
FileExtension = ".txt",
Size = 42
});
Action act = () => _validator.Validate(result);
act.Should()
.Throw<CalendarEventComposeValidationException>()
.WithMessage(string.Format(Translator.CalendarEventCompose_ValidationMissingAttachment, "missing.txt"));
}
[Fact]
public void Validate_WhenAttendeeEmailIsInvalid_ThrowsValidationException()
{
var result = CreateValidResult();
result.Attendees.Add(new CalendarEventAttendee
{
Id = Guid.NewGuid(),
CalendarItemId = Guid.Empty,
Email = "not-an-email"
});
Action act = () => _validator.Validate(result);
act.Should()
.Throw<CalendarEventComposeValidationException>()
.WithMessage(Translator.CalendarEventCompose_ValidationInvalidAttendee);
}
[Fact]
public void Validate_WhenAttendeeEmailIsDuplicated_ThrowsValidationException()
{
var result = CreateValidResult();
result.Attendees.Add(new CalendarEventAttendee
{
Id = Guid.NewGuid(),
CalendarItemId = Guid.Empty,
Email = "person@example.com"
});
result.Attendees.Add(new CalendarEventAttendee
{
Id = Guid.NewGuid(),
CalendarItemId = Guid.Empty,
Email = "PERSON@example.com"
});
Action act = () => _validator.Validate(result);
act.Should()
.Throw<CalendarEventComposeValidationException>()
.WithMessage(Translator.CalendarEventCompose_ValidationInvalidAttendee);
}
[Fact]
public void Validate_WhenCalendarIdIsMissing_ThrowsValidationException()
{
var result = CreateValidResult();
result.CalendarId = Guid.Empty;
Action act = () => _validator.Validate(result);
act.Should()
.Throw<CalendarEventComposeValidationException>()
.WithMessage(Translator.CalendarEventCompose_ValidationMissingCalendar);
}
private static CalendarEventComposeResult CreateValidResult()
{
return new CalendarEventComposeResult
{
CalendarId = Guid.NewGuid(),
AccountId = Guid.NewGuid(),
Title = "Design review",
StartDate = new DateTime(2026, 3, 7, 13, 30, 0),
EndDate = new DateTime(2026, 3, 7, 14, 0, 0),
TimeZoneId = TimeZoneInfo.Local.Id,
SelectedReminders =
[
new Reminder
{
Id = Guid.NewGuid(),
DurationInSeconds = 900
}
]
};
}
}
@@ -7,8 +7,8 @@ using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Calendar; using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Shared; using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums; using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Interfaces;
@@ -33,6 +33,11 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
public MailAccount Account { get; set; } public MailAccount Account { get; set; }
public ObservableCollection<IMailItemFolder> CurrentFolders { get; set; } = []; public ObservableCollection<IMailItemFolder> CurrentFolders { get; set; } = [];
public ObservableCollection<AccountCalendar> AccountCalendars { get; set; } = []; public ObservableCollection<AccountCalendar> AccountCalendars { get; set; } = [];
public ObservableCollection<AccountCalendarSettingsItemViewModel> AccountCalendarSettingsItems { get; } = [];
public ObservableCollection<AccountCalendarShowAsOption> ShowAsOptions { get; } = [];
[ObservableProperty]
public partial AccountCalendar SelectedPrimaryCalendar { get; set; }
[ObservableProperty] [ObservableProperty]
public partial int SelectedTabIndex { get; set; } = 1; // Default to Mail tab public partial int SelectedTabIndex { get; set; } = 1; // Default to Mail tab
@@ -70,6 +75,12 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
_folderService = folderService; _folderService = folderService;
_calendarService = calendarService; _calendarService = calendarService;
_statePersistanceService = statePersistanceService; _statePersistanceService = statePersistanceService;
ShowAsOptions.Add(new AccountCalendarShowAsOption(CalendarItemShowAs.Free, Translator.CalendarShowAs_Free));
ShowAsOptions.Add(new AccountCalendarShowAsOption(CalendarItemShowAs.Tentative, Translator.CalendarShowAs_Tentative));
ShowAsOptions.Add(new AccountCalendarShowAsOption(CalendarItemShowAs.Busy, Translator.CalendarShowAs_Busy));
ShowAsOptions.Add(new AccountCalendarShowAsOption(CalendarItemShowAs.OutOfOffice, Translator.CalendarShowAs_OutOfOffice));
ShowAsOptions.Add(new AccountCalendarShowAsOption(CalendarItemShowAs.WorkingElsewhere, Translator.CalendarShowAs_WorkingElsewhere));
} }
[RelayCommand] [RelayCommand]
@@ -165,20 +176,40 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
{ {
var calendars = await _calendarService.GetAccountCalendarsAsync(Account.Id); var calendars = await _calendarService.GetAccountCalendarsAsync(Account.Id);
await ExecuteUIThread(() =>
{
AccountCalendars.Clear(); AccountCalendars.Clear();
AccountCalendarSettingsItems.Clear();
foreach (var calendar in calendars) foreach (var calendar in calendars)
{ {
AccountCalendars.Add(calendar); AccountCalendars.Add(calendar);
AccountCalendarSettingsItems.Add(new AccountCalendarSettingsItemViewModel(calendar, ShowAsOptions));
} }
});
SelectedPrimaryCalendar = AccountCalendars.FirstOrDefault(calendar => calendar.IsPrimary) ?? AccountCalendars.FirstOrDefault();
} }
[RelayCommand] public AccountCalendarShowAsOption GetShowAsOption(CalendarItemShowAs showAs)
private void CalendarItemClicked(AccountCalendar calendar) => ShowAsOptions.FirstOrDefault(option => option.ShowAs == showAs) ?? ShowAsOptions.First();
public async Task UpdateCalendarSynchronizationAsync(AccountCalendar calendar, bool isEnabled)
{ {
if (calendar == null) return; if (calendar == null || calendar.IsSynchronizationEnabled == isEnabled)
return;
// Navigate to calendar settings page with breadcrumb calendar.IsSynchronizationEnabled = isEnabled;
Messenger.Send(new BreadcrumbNavigationRequested(calendar.Name, WinoPage.CalendarAccountSettingsPage, calendar)); await _calendarService.UpdateAccountCalendarAsync(calendar);
}
public async Task UpdateCalendarDefaultShowAsAsync(AccountCalendar calendar, AccountCalendarShowAsOption option)
{
if (calendar == null || option == null || calendar.DefaultShowAs == option.ShowAs)
return;
calendar.DefaultShowAs = option.ShowAs;
await _calendarService.UpdateAccountCalendarAsync(calendar);
} }
protected override async void OnPropertyChanged(PropertyChangedEventArgs e) protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
@@ -209,6 +240,50 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
Account.Preferences.IsTaskbarBadgeEnabled = IsTaskbarBadgeEnabled; Account.Preferences.IsTaskbarBadgeEnabled = IsTaskbarBadgeEnabled;
await _accountService.UpdateAccountAsync(Account); await _accountService.UpdateAccountAsync(Account);
break; break;
case nameof(SelectedPrimaryCalendar) when SelectedPrimaryCalendar != null:
foreach (var calendar in AccountCalendars)
{
calendar.IsPrimary = calendar.Id == SelectedPrimaryCalendar.Id;
}
await _calendarService.SetPrimaryCalendarAsync(Account.Id, SelectedPrimaryCalendar.Id);
break;
} }
} }
} }
public sealed class AccountCalendarShowAsOption
{
public CalendarItemShowAs ShowAs { get; }
public string DisplayText { get; }
public AccountCalendarShowAsOption(CalendarItemShowAs showAs, string displayText)
{
ShowAs = showAs;
DisplayText = displayText;
}
}
public partial class AccountCalendarSettingsItemViewModel : ObservableObject
{
public AccountCalendar Calendar { get; }
public ObservableCollection<AccountCalendarShowAsOption> ShowAsOptions { get; }
public string Name => Calendar.Name;
public string TimeZone => Calendar.TimeZone;
public string BackgroundColorHex => Calendar.BackgroundColorHex;
[ObservableProperty]
public partial bool IsSynchronizationEnabled { get; set; }
[ObservableProperty]
public partial AccountCalendarShowAsOption SelectedShowAsOption { get; set; }
public AccountCalendarSettingsItemViewModel(AccountCalendar calendar, ObservableCollection<AccountCalendarShowAsOption> showAsOptions)
{
Calendar = calendar;
ShowAsOptions = showAsOptions;
IsSynchronizationEnabled = calendar.IsSynchronizationEnabled;
SelectedShowAsOption = showAsOptions.FirstOrDefault(option => option.ShowAs == calendar.DefaultShowAs) ?? showAsOptions.FirstOrDefault();
}
}
@@ -28,12 +28,12 @@
<ScrollViewer Margin="0,8,0,0"> <ScrollViewer Margin="0,8,0,0">
<ItemsControl ItemsSource="{x:Bind AvailableGroups}"> <ItemsControl ItemsSource="{x:Bind AvailableGroups}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="calendar:CalendarPickerAccountGroup">
<StackPanel Margin="0,0,0,12" Spacing="6"> <StackPanel Margin="0,0,0,12" Spacing="6">
<TextBlock FontWeight="SemiBold"> <TextBlock FontWeight="SemiBold">
<Run Text="{Binding Account.Name}" /> <Run Text="{x:Bind Account.Name}" />
<Run Text=" (" /> <Run Text=" (" />
<Run Text="{Binding Account.Address}" /> <Run Text="{x:Bind Account.Address}" />
<Run Text=")" /> <Run Text=")" />
</TextBlock> </TextBlock>
@@ -41,10 +41,10 @@
IsItemClickEnabled="True" IsItemClickEnabled="True"
ItemClick="CalendarClicked" ItemClick="CalendarClicked"
ItemContainerStyle="{StaticResource CalendarPickerListItemStyle}" ItemContainerStyle="{StaticResource CalendarPickerListItemStyle}"
ItemsSource="{Binding Calendars}" ItemsSource="{x:Bind Calendars}"
SelectionMode="None"> SelectionMode="None">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="sharedCalendar:AccountCalendar">
<Grid ColumnSpacing="10"> <Grid ColumnSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -55,12 +55,12 @@
Width="14" Width="14"
Height="14" Height="14"
VerticalAlignment="Center" VerticalAlignment="Center"
Fill="{ThemeResource AccentFillColorDefaultBrush}" /> Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
<TextBlock <TextBlock
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Name}" /> Text="{x:Bind Name}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
+1
View File
@@ -157,6 +157,7 @@
<coreControls:WinoFontIcon Icon="NewMail" /> <coreControls:WinoFontIcon Icon="NewMail" />
</muxc:NavigationViewItem.Icon> </muxc:NavigationViewItem.Icon>
<TextBlock <TextBlock
Margin="0,-2,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
FontSize="16" FontSize="16"
Style="{StaticResource FlyoutPickerTitleTextBlockStyle}" Style="{StaticResource FlyoutPickerTitleTextBlockStyle}"
@@ -290,6 +290,18 @@ public class PreferencesService(IConfigurationService configurationService) : Ob
set => SaveProperty(propertyName: nameof(DefaultSnoozeDurationInMinutes), value); set => SaveProperty(propertyName: nameof(DefaultSnoozeDurationInMinutes), value);
} }
public NewEventButtonBehavior NewEventButtonBehavior
{
get => _configurationService.Get(nameof(NewEventButtonBehavior), NewEventButtonBehavior.AskEachTime);
set => SetPropertyAndSave(nameof(NewEventButtonBehavior), value);
}
public Guid? DefaultNewEventCalendarId
{
get => _configurationService.Get<Guid?>(nameof(DefaultNewEventCalendarId), null);
set => SetPropertyAndSave(nameof(DefaultNewEventCalendarId), value);
}
public int EmailSyncIntervalMinutes public int EmailSyncIntervalMinutes
{ {
get => _configurationService.Get(nameof(EmailSyncIntervalMinutes), 3); get => _configurationService.Get(nameof(EmailSyncIntervalMinutes), 3);
@@ -9,7 +9,7 @@
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="controls:WebViewEditorControl"> <ControlTemplate TargetType="controls:WebViewEditorControl">
<Grid CornerRadius="3"> <Grid CornerRadius="{StaticResource ControlCornerRadius}">
<Grid Background="White" Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsEditorDarkMode, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}" /> <Grid Background="White" Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=IsEditorDarkMode, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}" />
<WebView2 x:Name="WebView" /> <WebView2 x:Name="WebView" />
</Grid> </Grid>
@@ -14,6 +14,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:interfaces="using:Wino.Core.Domain.Interfaces" xmlns:interfaces="using:Wino.Core.Domain.Interfaces"
xmlns:local="using:Wino.Views" xmlns:local="using:Wino.Views"
xmlns:mailViewModels="using:Wino.Mail.ViewModels"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
@@ -50,7 +51,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Checked="SyncFolderToggled" Checked="SyncFolderToggled"
IsChecked="{x:Bind IsSynchronizationEnabled, Mode=OneWay}" IsChecked="{x:Bind IsSynchronizationEnabled, Mode=OneWay}"
Tag="{Binding}" Tag="{x:Bind}"
Unchecked="SyncFolderToggled" Unchecked="SyncFolderToggled"
Visibility="{x:Bind IsMoveTarget}" /> Visibility="{x:Bind IsMoveTarget}" />
@@ -61,7 +62,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Checked="UnreadBadgeCheckboxToggled" Checked="UnreadBadgeCheckboxToggled"
IsChecked="{x:Bind ShowUnreadCount, Mode=OneWay}" IsChecked="{x:Bind ShowUnreadCount, Mode=OneWay}"
Tag="{Binding}" Tag="{x:Bind}"
Unchecked="UnreadBadgeCheckboxToggled" Unchecked="UnreadBadgeCheckboxToggled"
Visibility="{x:Bind IsMoveTarget}" /> Visibility="{x:Bind IsMoveTarget}" />
</Grid> </Grid>
@@ -296,27 +297,72 @@
Visibility="Collapsed"> Visibility="Collapsed">
<StackPanel MaxWidth="900" Spacing="12"> <StackPanel MaxWidth="900" Spacing="12">
<!-- Calendars List --> <controls:SettingsCard Description="{x:Bind domain:Translator.CalendarAccountSettings_PrimaryCalendarDescription, Mode=OneTime}" Header="{x:Bind domain:Translator.CalendarAccountSettings_PrimaryCalendar, Mode=OneTime}">
<ItemsControl ItemsSource="{x:Bind ViewModel.AccountCalendars, Mode=OneWay}"> <controls:SettingsCard.HeaderIcon>
<ItemsControl.ItemTemplate> <FontIcon Glyph="&#xE735;" />
</controls:SettingsCard.HeaderIcon>
<ComboBox ItemsSource="{x:Bind ViewModel.AccountCalendars, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedPrimaryCalendar, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendar:AccountCalendar"> <DataTemplate x:DataType="calendar:AccountCalendar">
<controls:SettingsCard <TextBlock Text="{x:Bind Name}" />
Margin="0,0,0,4" </DataTemplate>
ActionIcon="{ui:FontIcon Glyph=&#xE974;}" </ComboBox.ItemTemplate>
Click="CalendarItemClicked" </ComboBox>
CommandParameter="{x:Bind}" </controls:SettingsCard>
<!-- Calendars List -->
<ItemsControl ItemsSource="{x:Bind ViewModel.AccountCalendarSettingsItems, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="mailViewModels:AccountCalendarSettingsItemViewModel">
<controls:SettingsExpander
Margin="0,0,0,8"
Description="{x:Bind TimeZone, Mode=OneWay}" Description="{x:Bind TimeZone, Mode=OneWay}"
Header="{x:Bind Name, Mode=OneWay}" Header="{x:Bind Name, Mode=OneWay}"
IsClickEnabled="True"> IsExpanded="False">
<controls:SettingsExpander.HeaderIcon>
<SymbolIcon Foreground="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" Symbol="Calendar" />
</controls:SettingsExpander.HeaderIcon>
<controls:SettingsExpander.Items>
<controls:SettingsCard Description="{x:Bind domain:Translator.CalendarAccountSettings_AccountColorDescription, Mode=OneTime}" Header="{x:Bind domain:Translator.CalendarAccountSettings_AccountColor, Mode=OneTime}">
<controls:SettingsCard.HeaderIcon> <controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE163;" /> <FontIcon Glyph="&#xE790;" />
</controls:SettingsCard.HeaderIcon> </controls:SettingsCard.HeaderIcon>
<Border <Border
Width="32" Width="32"
Height="32" Height="32"
Background="{x:Bind BackgroundColorHex, Mode=OneWay}" Background="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}"
CornerRadius="4" /> CornerRadius="4" />
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard Description="{x:Bind domain:Translator.CalendarAccountSettings_SyncEnabledDescription, Mode=OneTime}" Header="{x:Bind domain:Translator.CalendarAccountSettings_SyncEnabled, Mode=OneTime}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE895;" />
</controls:SettingsCard.HeaderIcon>
<ToggleSwitch
IsOn="{x:Bind IsSynchronizationEnabled, Mode=OneWay}"
Tag="{x:Bind}"
Toggled="CalendarSynchronizationToggled" />
</controls:SettingsCard>
<controls:SettingsCard Description="{x:Bind domain:Translator.CalendarAccountSettings_DefaultShowAsDescription, Mode=OneTime}" Header="{x:Bind domain:Translator.CalendarAccountSettings_DefaultShowAs, Mode=OneTime}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE163;" />
</controls:SettingsCard.HeaderIcon>
<ComboBox
ItemsSource="{x:Bind ShowAsOptions, Mode=OneWay}"
SelectedItem="{x:Bind SelectedShowAsOption, Mode=OneWay}"
SelectionChanged="CalendarShowAsSelectionChanged"
Tag="{x:Bind}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="mailViewModels:AccountCalendarShowAsOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
@@ -1,8 +1,8 @@
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation; using Microsoft.UI.Xaml.Navigation;
using Wino.Core.Domain.Entities.Calendar;
using Wino.Core.Domain.Models.Folders; using Wino.Core.Domain.Models.Folders;
using Wino.Mail.ViewModels;
using Wino.Views.Abstract; using Wino.Views.Abstract;
namespace Wino.Views; namespace Wino.Views;
@@ -37,11 +37,21 @@ public sealed partial class AccountDetailsPage : AccountDetailsPageAbstract
} }
} }
private void CalendarItemClicked(object sender, RoutedEventArgs e) private async void CalendarSynchronizationToggled(object sender, RoutedEventArgs e)
{ {
if (sender is CommunityToolkit.WinUI.Controls.SettingsCard settingsCard && settingsCard.CommandParameter is AccountCalendar calendar) if (sender is ToggleSwitch { Tag: AccountCalendarSettingsItemViewModel calendarItem } toggleSwitch)
{ {
ViewModel.CalendarItemClickedCommand?.Execute(calendar); calendarItem.IsSynchronizationEnabled = toggleSwitch.IsOn;
await ViewModel.UpdateCalendarSynchronizationAsync(calendarItem.Calendar, toggleSwitch.IsOn);
}
}
private async void CalendarShowAsSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ComboBox { Tag: AccountCalendarSettingsItemViewModel calendarItem, SelectedItem: AccountCalendarShowAsOption option })
{
calendarItem.SelectedShowAsOption = option;
await ViewModel.UpdateCalendarDefaultShowAsAsync(calendarItem.Calendar, option);
} }
} }
@@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract" xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract"
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="using:Wino.Core.Domain" xmlns:domain="using:Wino.Core.Domain"
@@ -56,16 +57,6 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.IsSyncEnabled, Mode=TwoWay}" /> <ToggleSwitch IsOn="{x:Bind ViewModel.IsSyncEnabled, Mode=TwoWay}" />
</controls:SettingsCard> </controls:SettingsCard>
<!-- Primary Calendar -->
<controls:SettingsCard
Description="{x:Bind domain:Translator.CalendarAccountSettings_PrimaryCalendarDescription, Mode=OneTime}"
Header="{x:Bind domain:Translator.CalendarAccountSettings_PrimaryCalendar, Mode=OneTime}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE735;" />
</controls:SettingsCard.HeaderIcon>
<ToggleSwitch IsOn="{x:Bind ViewModel.IsPrimaryCalendar, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Default Show As Status --> <!-- Default Show As Status -->
<controls:SettingsCard <controls:SettingsCard
Description="{x:Bind domain:Translator.CalendarAccountSettings_DefaultShowAsDescription, Mode=OneTime}" Description="{x:Bind domain:Translator.CalendarAccountSettings_DefaultShowAsDescription, Mode=OneTime}"
@@ -75,9 +66,14 @@
</controls:SettingsCard.HeaderIcon> </controls:SettingsCard.HeaderIcon>
<ComboBox <ComboBox
MinWidth="150" MinWidth="150"
DisplayMemberPath="DisplayText"
ItemsSource="{x:Bind ViewModel.ShowAsOptions, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.ShowAsOptions, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedDefaultShowAsOption, Mode=TwoWay}" /> SelectedItem="{x:Bind ViewModel.SelectedDefaultShowAsOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:ShowAsOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsCard> </controls:SettingsCard>
</StackPanel> </StackPanel>
@@ -82,10 +82,10 @@
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
Background="Transparent" Background="Transparent"
DisplayType="{x:Bind ViewModel.StatePersistenceService.CalendarDisplayType, Mode=OneWay}"
FontSize="14" FontSize="14"
FontWeight="Normal" FontWeight="Normal"
IsHitTestVisible="False" IsHitTestVisible="False"
DisplayType="{x:Bind ViewModel.StatePersistenceService.CalendarDisplayType, Mode=OneWay}"
ItemsSource="{x:Bind ViewModel.DateNavigationHeaderItems}" ItemsSource="{x:Bind ViewModel.DateNavigationHeaderItems}"
SelectedIndex="{x:Bind ViewModel.SelectedDateNavigationHeaderIndex, Mode=OneWay}"> SelectedIndex="{x:Bind ViewModel.SelectedDateNavigationHeaderIndex, Mode=OneWay}">
<FlipView.ItemTemplate> <FlipView.ItemTemplate>
@@ -156,13 +156,24 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Button <Button
Margin="14,12,14,0" Margin="10,12,10,0"
Padding="4,12"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Command="{x:Bind ViewModel.NewEventCommand}" HorizontalContentAlignment="Left"
Style="{StaticResource AccentButtonStyle}"> Background="Transparent"
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Spacing="8"> BorderBrush="Transparent"
Command="{x:Bind ViewModel.NewEventCommand}">
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<coreControls:WinoFontIcon FontSize="16" Icon="NewMail" /> <coreControls:WinoFontIcon FontSize="16" Icon="NewMail" />
<TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_NewEventButton}" /> <TextBlock
Margin="10,-2,0,0"
VerticalAlignment="Center"
FontSize="16"
Style="{StaticResource FlyoutPickerTitleTextBlockStyle}"
Text="{x:Bind domain:Translator.CalendarEventCompose_NewEventButton}" />
</StackPanel> </StackPanel>
</Button> </Button>
@@ -279,7 +290,10 @@
<ListView.Items> <ListView.Items>
<ListViewItem> <ListViewItem>
<StackPanel Orientation="Horizontal" Spacing="12"> <StackPanel Orientation="Horizontal" Spacing="12">
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" FontSize="16" Glyph="&#xE77B;" /> <FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Glyph="&#xE77B;" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.MenuManageAccounts}" /> <TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.MenuManageAccounts}" />
</StackPanel> </StackPanel>
@@ -10,6 +10,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="using:Wino.Calendar.ViewModels.Data" xmlns:data="using:Wino.Calendar.ViewModels.Data"
xmlns:domain="using:Wino.Core.Domain" xmlns:domain="using:Wino.Core.Domain"
xmlns:helpers="using:Wino.Helpers"
xmlns:mailControls="using:Wino.Mail.Controls" xmlns:mailControls="using:Wino.Mail.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shared="using:Wino.Core.Domain.Entities.Shared" xmlns:shared="using:Wino.Core.Domain.Entities.Shared"
@@ -24,247 +25,311 @@
TargetType="Button"> TargetType="Button">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="12,8" /> <Setter Property="Padding" Value="8" />
<Setter Property="MinWidth" Value="0" /> <Setter Property="MinWidth" Value="0" />
<Setter Property="MinHeight" Value="0" /> <Setter Property="MinHeight" Value="0" />
</Style> </Style>
<Style <Style
x:Key="ComposeCreateButtonStyle" x:Key="FieldToggleButtonStyle"
BasedOn="{StaticResource DefaultButtonStyle}" BasedOn="{StaticResource DefaultToggleButtonStyle}"
TargetType="Button"> TargetType="ToggleButton">
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
<Setter Property="Padding" Value="16,8" />
</Style>
<Style x:Key="EventDetailsPanelGridStyle" TargetType="Grid">
<Setter Property="Padding" Value="12,6" /> <Setter Property="Padding" Value="12,6" />
<Setter Property="Margin" Value="0,12" /> <Setter Property="MinWidth" Value="0" />
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorSecondaryBrush}" /> <Setter Property="MinHeight" Value="0" />
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="4" />
</Style> </Style>
<Style <DataTemplate x:Key="AttendeeSuggestionTemplate" x:DataType="shared:AccountContact">
x:Key="EventDetailsPanelTitleStyle"
BasedOn="{StaticResource SubtitleTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Margin" Value="0,0,0,20" />
</Style>
<DataTemplate x:Key="AttendeeSuggestionTemplate">
<StackPanel Padding="8,4" Orientation="Vertical"> <StackPanel Padding="8,4" Orientation="Vertical">
<TextBlock FontWeight="SemiBold" Text="{Binding Name}" /> <TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
<TextBlock FontSize="12" Text="{Binding Address}" /> <TextBlock FontSize="12" Text="{x:Bind Address}" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
<DataTemplate x:Key="AttendeeTokenTemplate"> <DataTemplate x:Key="AttendeeTokenTemplate" x:DataType="data:CalendarComposeAttendeeViewModel">
<TextBlock Text="{Binding DisplayName}" /> <TextBlock Text="{x:Bind DisplayName}" />
</DataTemplate> </DataTemplate>
</Page.Resources> </Page.Resources>
<Grid Padding="20"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Border <!-- Top Bar -->
VerticalAlignment="Top" <Grid
Padding="16,10"
Background="{ThemeResource WinoContentZoneBackgroud}" Background="{ThemeResource WinoContentZoneBackgroud}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}" CornerRadius="{StaticResource ControlCornerRadius}">
BorderThickness="1" <Grid.ColumnDefinitions>
CornerRadius="7"> <ColumnDefinition Width="Auto" />
<ScrollViewer <ColumnDefinition Width="*" />
HorizontalScrollBarVisibility="Auto" <ColumnDefinition Width="Auto" />
HorizontalScrollMode="Enabled" </Grid.ColumnDefinitions>
VerticalScrollBarVisibility="Disabled"
VerticalScrollMode="Disabled"> <!-- Left: Save + Discard -->
<StackPanel <StackPanel Orientation="Horizontal" Spacing="8">
Padding="8" <Button Command="{x:Bind ViewModel.CreateCommand}">
Orientation="Horizontal" <Button.Style>
Spacing="8"> <Style BasedOn="{StaticResource AccentButtonStyle}" TargetType="Button">
<Button Command="{x:Bind ViewModel.CreateCommand}" Style="{StaticResource ComposeCreateButtonStyle}"> <Setter Property="Padding" Value="16,6" />
</Style>
</Button.Style>
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<coreControls:WinoFontIcon FontSize="16" Icon="Save" /> <coreControls:WinoFontIcon FontSize="16" Icon="Save" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Create}" /> <TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Save}" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Command="{x:Bind ViewModel.CancelCommand}" Style="{StaticResource TransparentActionButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8">
<coreControls:WinoFontIcon FontSize="16" Icon="Dismiss" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Discard}" />
</StackPanel>
</Button>
</StackPanel>
<!-- Right: Calendar, Show As, Reminder -->
<StackPanel
Grid.Column="2"
Orientation="Horizontal"
Spacing="12">
<!-- Calendar -->
<StackPanel <StackPanel
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="8"> Spacing="8">
<coreControls:WinoFontIcon FontSize="16" Icon="CalendarShowAs" /> <coreControls:WinoFontIcon FontSize="14" Icon="Calendar" />
<ComboBox <Button
Width="190" Padding="10,6"
DisplayMemberPath="Name" HorizontalContentAlignment="Left"
ItemsSource="{x:Bind ViewModel.AvailableCalendars}" Style="{StaticResource DefaultButtonStyle}">
PlaceholderText="{x:Bind domain:Translator.CalendarEventCompose_SelectCalendar}" <Button.Flyout>
SelectedItem="{x:Bind ViewModel.SelectedCalendar, Mode=TwoWay}"> <Flyout Placement="BottomEdgeAlignedLeft">
<ComboBox.ItemTemplate> <ScrollViewer MaxHeight="360">
<DataTemplate> <ItemsControl ItemsSource="{x:Bind ViewModel.AvailableCalendarGroups, Mode=OneWay}">
<StackPanel Orientation="Vertical" Spacing="2"> <ItemsControl.ItemTemplate>
<TextBlock Text="{Binding Name}" /> <DataTemplate x:DataType="data:GroupedAccountCalendarViewModel">
<StackPanel
MinWidth="320"
Padding="0,0,0,12"
Spacing="6">
<TextBlock FontWeight="SemiBold">
<Run Text="{x:Bind Account.Name}" />
<Run Text=" (" />
<Run Text="{x:Bind Account.Address}" />
<Run Text=")" />
</TextBlock>
<ListView
IsItemClickEnabled="True"
ItemClick="ComposeCalendarClicked"
ItemsSource="{x:Bind AccountCalendars}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:AccountCalendarViewModel">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse
Width="14"
Height="14"
VerticalAlignment="Center"
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
<TextBlock <TextBlock
FontSize="12" Grid.Column="1"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" VerticalAlignment="Center"
Text="{Binding Account.Address}" /> Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Flyout>
</Button.Flyout>
<StackPanel Orientation="Horizontal" Spacing="8">
<Ellipse
Width="10"
Height="10"
VerticalAlignment="Center"
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(ViewModel.SelectedCalendar.BackgroundColorHex), Mode=OneWay}" />
<StackPanel Spacing="0">
<TextBlock Text="{x:Bind ViewModel.SelectedCalendarDisplayText, Mode=OneWay}" />
<TextBlock
FontSize="11"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind ViewModel.SelectedCalendarAccountText, Mode=OneWay}"
Visibility="{x:Bind helpers:XamlHelpers.StringToVisibilityConverter(ViewModel.SelectedCalendarAccountText), Mode=OneWay}" />
</StackPanel>
<FontIcon Glyph="&#xE70D;" />
</StackPanel>
</Button>
</StackPanel>
<!-- Show As -->
<StackPanel
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<coreControls:WinoFontIcon FontSize="14" Icon="CalendarShowAs" />
<ComboBox
VerticalAlignment="Center"
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:ShowAsOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
<!-- Reminder -->
<StackPanel <StackPanel
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="8"> Spacing="8">
<coreControls:WinoFontIcon FontSize="16" Icon="CalendarShowAs" /> <coreControls:WinoFontIcon FontSize="14" Icon="Reminder" />
<ComboBox <ComboBox
Width="150"
DisplayMemberPath="DisplayText"
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}" />
</StackPanel>
<StackPanel
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<coreControls:WinoFontIcon FontSize="16" Icon="Reminder" />
<ComboBox
Width="150"
DisplayMemberPath="DisplayText"
ItemsSource="{x:Bind ViewModel.ReminderOptions}" ItemsSource="{x:Bind ViewModel.ReminderOptions}"
SelectedItem="{x:Bind ViewModel.SelectedReminderOption, Mode=TwoWay}" /> SelectedItem="{x:Bind ViewModel.SelectedReminderOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:ReminderOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</ScrollViewer>
</Border>
<ScrollViewer Grid.Row="1">
<Grid ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="DetailsGrid" Style="{StaticResource EventDetailsPanelGridStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="{x:Bind domain:Translator.CalendarEventDetails_Details}" />
<TextBox
Grid.Row="1"
Header="{x:Bind domain:Translator.CalendarEventCompose_Title}"
PlaceholderText="{x:Bind domain:Translator.CalendarEventCompose_TitlePlaceholder}"
Text="{x:Bind ViewModel.Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Grid
Grid.Row="2"
Margin="0,12,0,0"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DatePicker
Grid.Column="0"
Date="{x:Bind ViewModel.StartDate, Mode=TwoWay}"
Header="{x:Bind domain:Translator.CalendarEventCompose_StartDate}" />
<ToggleButton
Grid.Column="1"
Margin="0,30,0,0"
IsChecked="{x:Bind ViewModel.IsAllDay, Mode=TwoWay}">
<TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_AllDay}" />
</ToggleButton>
<TimePicker
Grid.Column="2"
ClockIdentifier="{x:Bind ViewModel.TimePickerClockIdentifier, Mode=OneWay}"
Header="{x:Bind domain:Translator.CalendarEventCompose_StartTime}"
IsEnabled="{x:Bind ViewModel.IsAllDay, Mode=OneWay}"
SelectedTime="{x:Bind ViewModel.StartTime, Mode=TwoWay}" />
<TimePicker
Grid.Column="3"
ClockIdentifier="{x:Bind ViewModel.TimePickerClockIdentifier, Mode=OneWay}"
Header="{x:Bind domain:Translator.CalendarEventCompose_EndTime}"
IsEnabled="{x:Bind ViewModel.IsAllDay, Mode=OneWay}"
SelectedTime="{x:Bind ViewModel.EndTime, Mode=TwoWay}" />
</Grid> </Grid>
<!-- Scrollable Content -->
<ScrollViewer Grid.Row="1" MaxWidth="1200">
<StackPanel
Padding="40,24,40,40"
HorizontalAlignment="Stretch"
Spacing="0">
<!-- Title -->
<TextBox
FontSize="24"
PlaceholderText="{x:Bind domain:Translator.CalendarEventCompose_TitlePlaceholder}"
Text="{x:Bind ViewModel.Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="TextBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderBrush" Value="Transparent" />
<!--<Setter Property="Padding" Value="0,8" />-->
</Style>
</TextBox.Style>
</TextBox>
<!-- Divider -->
<Border
Height="1"
Margin="0,0,0,20"
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
<!-- Date Row -->
<StackPanel
Margin="0,0,0,12"
Orientation="Horizontal"
Spacing="12">
<DatePicker Date="{x:Bind ViewModel.StartDate, Mode=TwoWay}" Header="{x:Bind domain:Translator.CalendarEventCompose_StartDate}" />
<ToggleButton
VerticalAlignment="Bottom"
Content="{x:Bind domain:Translator.CalendarEventCompose_AllDay}"
IsChecked="{x:Bind ViewModel.IsAllDay, Mode=TwoWay}"
Style="{StaticResource FieldToggleButtonStyle}" />
</StackPanel>
<!-- Time Row -->
<StackPanel
Margin="0,0,0,8"
Orientation="Horizontal"
Spacing="12"
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsAllDay), Mode=OneWay}">
<TimePicker
ClockIdentifier="{x:Bind ViewModel.TimePickerClockIdentifier, Mode=OneWay}"
Header="{x:Bind domain:Translator.CalendarEventCompose_StartTime}"
SelectedTime="{x:Bind ViewModel.StartTime, Mode=TwoWay}" />
<TextBlock
Margin="0,28,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="—" />
<TimePicker
ClockIdentifier="{x:Bind ViewModel.TimePickerClockIdentifier, Mode=OneWay}"
Header="{x:Bind domain:Translator.CalendarEventCompose_EndTime}"
SelectedTime="{x:Bind ViewModel.EndTime, Mode=TwoWay}" />
</StackPanel>
<!-- All Day End Date -->
<DatePicker <DatePicker
Grid.Row="3" Margin="0,0,0,8"
Margin="0,12,0,0"
Date="{x:Bind ViewModel.AllDayEndDate, Mode=TwoWay}" Date="{x:Bind ViewModel.AllDayEndDate, Mode=TwoWay}"
Header="{x:Bind domain:Translator.CalendarEventCompose_EndDate}" Header="{x:Bind domain:Translator.CalendarEventCompose_EndDate}"
Visibility="{x:Bind ViewModel.IsAllDay, Mode=OneWay}" /> Visibility="{x:Bind ViewModel.IsAllDay, Mode=OneWay}" />
<Grid <!-- Date/Time Summary -->
Grid.Row="4" <TextBlock
Margin="0,12,0,0" Margin="0,0,0,16"
ColumnSpacing="12"> FontSize="13"
<Grid.ColumnDefinitions> Foreground="{ThemeResource TextFillColorSecondaryBrush}"
<ColumnDefinition Width="*" /> Text="{x:Bind ViewModel.RecurrenceSummary, Mode=OneWay}"
<ColumnDefinition Width="Auto" /> TextWrapping="Wrap" />
</Grid.ColumnDefinitions>
<!-- Location -->
<TextBox <TextBox
Grid.Column="0" Margin="0,0,0,12"
Header="{x:Bind domain:Translator.CalendarEventCompose_Location}"
PlaceholderText="{x:Bind domain:Translator.CalendarEventCompose_LocationPlaceholder}" PlaceholderText="{x:Bind domain:Translator.CalendarEventCompose_LocationPlaceholder}"
Text="{x:Bind ViewModel.Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> Text="{x:Bind ViewModel.Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<!-- Recurring Toggle -->
<ToggleButton <ToggleButton
Grid.Column="1" Margin="0,0,0,4"
Margin="0,30,0,0" IsChecked="{x:Bind ViewModel.IsRecurring, Mode=TwoWay}"
IsChecked="{x:Bind ViewModel.IsRecurring, Mode=TwoWay}"> Style="{StaticResource FieldToggleButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8">
<coreControls:WinoFontIcon FontSize="14" Icon="CalendarEventRepeat" />
<TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_Recurring}" /> <TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_Recurring}" />
</StackPanel>
</ToggleButton> </ToggleButton>
</Grid>
<!-- Recurrence Panel -->
<Border <Border
Grid.Row="5" Margin="0,8,0,4"
Margin="0,12,0,0" Padding="16"
Padding="12"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="6" CornerRadius="{StaticResource ControlCornerRadius}"
Visibility="{x:Bind ViewModel.IsRecurring, Mode=OneWay}"> Visibility="{x:Bind ViewModel.IsRecurring, Mode=OneWay}">
<StackPanel Spacing="12"> <StackPanel Spacing="12">
<Grid ColumnSpacing="8"> <Grid ColumnSpacing="8">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="140" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="170" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -279,9 +344,14 @@
Text="{x:Bind domain:Translator.CalendarEventCompose_Every}" /> Text="{x:Bind domain:Translator.CalendarEventCompose_Every}" />
<ComboBox <ComboBox
Grid.Column="3" Grid.Column="3"
DisplayMemberPath="DisplayText"
ItemsSource="{x:Bind ViewModel.RecurrenceFrequencyOptions}" ItemsSource="{x:Bind ViewModel.RecurrenceFrequencyOptions}"
SelectedItem="{x:Bind ViewModel.SelectedRecurrenceFrequencyOption, Mode=TwoWay}" /> SelectedItem="{x:Bind ViewModel.SelectedRecurrenceFrequencyOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:CalendarComposeFrequencyOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock <TextBlock
Grid.Column="4" Grid.Column="4"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -293,13 +363,13 @@
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="calendarViewModels:CalendarComposeWeekdayOption">
<ToggleButton <ToggleButton
Width="32" Width="32"
Height="32" Height="32"
Padding="0" Padding="0"
IsChecked="{Binding IsSelected, Mode=TwoWay}"> IsChecked="{x:Bind IsSelected, Mode=TwoWay}">
<TextBlock HorizontalAlignment="Center" Text="{Binding Label}" /> <TextBlock HorizontalAlignment="Center" Text="{x:Bind Label}" />
</ToggleButton> </ToggleButton>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
@@ -322,48 +392,36 @@
</StackPanel> </StackPanel>
</Border> </Border>
<TextBlock <!-- People & Attachments Toggle Row -->
Grid.Row="6" <StackPanel
Margin="0,12,0,0" Margin="0,12,0,4"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Orientation="Horizontal"
Text="{x:Bind ViewModel.RecurrenceSummary, Mode=OneWay}" Spacing="8">
TextWrapping="Wrap" /> <ToggleButton x:Name="PeopleToggle" Style="{StaticResource FieldToggleButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8">
<coreControls:WinoFontIcon FontSize="14" Icon="People" />
<TextBlock Text="{x:Bind domain:Translator.CalendarEventDetails_People}" />
</StackPanel>
</ToggleButton>
<Grid Grid.Row="7" Margin="0,12,0,0"> <ToggleButton x:Name="AttachmentsToggle" Style="{StaticResource FieldToggleButtonStyle}">
<Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Spacing="8">
<RowDefinition Height="Auto" /> <coreControls:WinoFontIcon FontSize="14" Icon="Attachment" />
<RowDefinition Height="*" /> <TextBlock Text="{x:Bind domain:Translator.CalendarEventDetails_Attachments}" />
</Grid.RowDefinitions> </StackPanel>
</ToggleButton>
<TextBlock </StackPanel>
Margin="0,0,0,8"
FontWeight="SemiBold"
Text="{x:Bind domain:Translator.CalendarEventCompose_Notes}" />
<mailControls:WebViewEditorControl
x:Name="NotesEditor"
Grid.Row="1"
Height="340"
IsEditorDarkMode="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=OneWay}"
IsEditorWebViewEditor="True" />
</Grid>
</Grid>
<Grid
x:Name="PeopleGrid"
Grid.Column="1"
Style="{StaticResource EventDetailsPanelGridStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="{x:Bind domain:Translator.CalendarEventDetails_People}" />
<!-- People Pane -->
<Border
Margin="0,8,0,0"
Padding="16"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{StaticResource ControlCornerRadius}"
Visibility="{x:Bind PeopleToggle.IsChecked, Mode=OneWay}">
<StackPanel Spacing="8">
<toolkit:TokenizingTextBox <toolkit:TokenizingTextBox
x:Name="AttendeeBox" x:Name="AttendeeBox"
Grid.Row="1"
BorderThickness="0" BorderThickness="0"
ItemsSource="{x:Bind ViewModel.Attendees, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.Attendees, Mode=OneWay}"
LostFocus="AddressBoxLostFocus" LostFocus="AddressBoxLostFocus"
@@ -374,13 +432,18 @@
TokenItemTemplate="{StaticResource AttendeeTokenTemplate}" /> TokenItemTemplate="{StaticResource AttendeeTokenTemplate}" />
<ListView <ListView
Grid.Row="2" Margin="-8,0,-8,-8"
Margin="-12,12,0,0"
ItemsSource="{x:Bind ViewModel.Attendees, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.Attendees, Mode=OneWay}"
SelectionMode="None"> SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="8,4" />
<Setter Property="MinHeight" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="data:CalendarComposeAttendeeViewModel">
<Grid Margin="0,6" ColumnSpacing="12"> <Grid ColumnSpacing="10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -388,146 +451,135 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<controls:ImagePreviewControl <controls:ImagePreviewControl
Width="40" Width="32"
Height="40" Height="32"
Address="{Binding Email}" Address="{x:Bind Email}"
DisplayNameOverride="{Binding DisplayName}" DisplayNameOverride="{x:Bind DisplayName}"
PreviewContact="{Binding ResolvedContact}" /> PreviewContact="{x:Bind ResolvedContact}" />
<StackPanel Grid.Column="1" Spacing="4"> <StackPanel
<TextBlock Grid.Column="1"
FontWeight="SemiBold" VerticalAlignment="Center"
Text="{Binding DisplayName}" Spacing="1">
TextWrapping="Wrap" />
<TextBlock <TextBlock
FontSize="13" FontSize="13"
Text="{x:Bind DisplayName}"
TextTrimming="CharacterEllipsis" />
<TextBlock
FontSize="11"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Email}" Text="{x:Bind Email}"
Visibility="{Binding HasDistinctDisplayName}" /> TextTrimming="CharacterEllipsis"
Visibility="{x:Bind HasDistinctDisplayName, Mode=OneWay}" />
</StackPanel> </StackPanel>
<Button <Button
Grid.Column="2" Grid.Column="2"
Padding="6,4"
VerticalAlignment="Center"
Click="RemoveAttendeeClicked" Click="RemoveAttendeeClicked"
Style="{StaticResource TransparentActionButtonStyle}" Style="{StaticResource TransparentActionButtonStyle}"
Tag="{Binding}"> Tag="{x:Bind}">
<coreControls:WinoFontIcon FontSize="14" Icon="Delete" /> <coreControls:WinoFontIcon FontSize="12" Icon="Delete" />
</Button> </Button>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
</Grid> </StackPanel>
</Border>
<Grid
x:Name="AttachmentsGrid"
Grid.Column="2"
Style="{StaticResource EventDetailsPanelGridStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource EventDetailsPanelTitleStyle}" Text="{x:Bind domain:Translator.CalendarEventDetails_Attachments}" />
<!-- Attachments Pane -->
<Border
Margin="0,8,0,0"
Padding="16"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="{StaticResource ControlCornerRadius}"
Visibility="{x:Bind AttachmentsToggle.IsChecked, Mode=OneWay}">
<StackPanel Spacing="8">
<Button <Button
Grid.Row="1"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Command="{x:Bind ViewModel.AddAttachmentsCommand}" Command="{x:Bind ViewModel.AddAttachmentsCommand}"
Style="{StaticResource TransparentActionButtonStyle}"> Style="{StaticResource TransparentActionButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<coreControls:WinoFontIcon FontSize="14" Icon="EventAccept" /> <coreControls:WinoFontIcon FontSize="14" Icon="AttachmentNew" />
<TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_AddAttachment}" /> <TextBlock Text="{x:Bind domain:Translator.CalendarEventCompose_AddAttachment}" />
</StackPanel> </StackPanel>
</Button> </Button>
<ListView <ListView
Grid.Row="2" Margin="-8,0,-8,-8"
Margin="-12,12,0,0"
ItemsSource="{x:Bind ViewModel.Attachments, Mode=OneWay}" ItemsSource="{x:Bind ViewModel.Attachments, Mode=OneWay}"
SelectionMode="None"> SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="8,4" />
<Setter Property="MinHeight" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate x:DataType="data:CalendarComposeAttachmentViewModel">
<Grid Height="56" ColumnSpacing="8"> <Grid Height="44" ColumnSpacing="8">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="40" /> <ColumnDefinition Width="32" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ContentControl <ContentControl
VerticalAlignment="Center" VerticalAlignment="Center"
Content="{Binding AttachmentType}" Content="{x:Bind AttachmentType}"
ContentTemplateSelector="{StaticResource FileTypeIconSelector}" /> ContentTemplateSelector="{StaticResource FileTypeIconSelector}" />
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center" VerticalAlignment="Center"
Spacing="2"> Spacing="1">
<TextBlock Text="{Binding FileName}" TextTrimming="CharacterEllipsis" />
<TextBlock <TextBlock
FontSize="12" FontSize="13"
Text="{x:Bind FileName}"
TextTrimming="CharacterEllipsis" />
<TextBlock
FontSize="11"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding ReadableSize}" /> Text="{x:Bind ReadableSize}" />
</StackPanel> </StackPanel>
<Button <Button
Grid.Column="2" Grid.Column="2"
Padding="6,4"
VerticalAlignment="Center"
Click="RemoveAttachmentClicked" Click="RemoveAttachmentClicked"
Style="{StaticResource TransparentActionButtonStyle}" Style="{StaticResource TransparentActionButtonStyle}"
Tag="{Binding}"> Tag="{x:Bind}">
<coreControls:WinoFontIcon FontSize="14" Icon="Delete" /> <coreControls:WinoFontIcon FontSize="12" Icon="Delete" />
</Button> </Button>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
</Grid> </StackPanel>
</Grid> </Border>
</ScrollViewer>
<VisualStateManager.VisualStateGroups> <!-- Notes Header -->
<VisualStateGroup x:Name="WindowWidthStates"> <StackPanel
<VisualState x:Name="WideState"> Margin="0,20,0,8"
<VisualState.StateTriggers> Orientation="Horizontal"
<AdaptiveTrigger MinWindowWidth="1200" /> Spacing="8">
</VisualState.StateTriggers> <coreControls:WinoFontIcon
</VisualState> FontSize="14"
<VisualState x:Name="MediumState"> Foreground="{ThemeResource TextFillColorSecondaryBrush}"
<VisualState.StateTriggers> Icon="Draft" />
<AdaptiveTrigger MinWindowWidth="800" /> <TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.CalendarEventCompose_Notes}" />
</VisualState.StateTriggers> </StackPanel>
<VisualState.Setters>
<Setter Target="DetailsGrid.(Grid.Row)" Value="0" /> <!-- Notes Editor -->
<Setter Target="DetailsGrid.(Grid.Column)" Value="0" /> <mailControls:WebViewEditorControl
<Setter Target="DetailsGrid.(Grid.RowSpan)" Value="2" /> x:Name="NotesEditor"
<Setter Target="PeopleGrid.(Grid.Row)" Value="0" /> MinHeight="600"
<Setter Target="PeopleGrid.(Grid.Column)" Value="1" /> IsEditorDarkMode="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=OneWay}"
<Setter Target="PeopleGrid.(Grid.ColumnSpan)" Value="2" /> IsEditorWebViewEditor="True" />
<Setter Target="PeopleGrid.(Grid.RowSpan)" Value="1" /> </StackPanel>
<Setter Target="AttachmentsGrid.(Grid.Row)" Value="1" /> </ScrollViewer>
<Setter Target="AttachmentsGrid.(Grid.Column)" Value="1" />
<Setter Target="AttachmentsGrid.(Grid.ColumnSpan)" Value="2" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NarrowState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="DetailsGrid.(Grid.Row)" Value="0" />
<Setter Target="DetailsGrid.(Grid.Column)" Value="0" />
<Setter Target="DetailsGrid.(Grid.ColumnSpan)" Value="3" />
<Setter Target="PeopleGrid.(Grid.Row)" Value="1" />
<Setter Target="PeopleGrid.(Grid.Column)" Value="0" />
<Setter Target="PeopleGrid.(Grid.ColumnSpan)" Value="3" />
<Setter Target="AttachmentsGrid.(Grid.Row)" Value="2" />
<Setter Target="AttachmentsGrid.(Grid.Column)" Value="0" />
<Setter Target="AttachmentsGrid.(Grid.ColumnSpan)" Value="3" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid> </Grid>
</abstract:CalendarEventComposePageAbstract> </abstract:CalendarEventComposePageAbstract>
@@ -137,6 +137,14 @@ public sealed partial class CalendarEventComposePage : CalendarEventComposePageA
} }
} }
private void ComposeCalendarClicked(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is AccountCalendarViewModel calendar)
{
ViewModel.SelectedCalendar = calendar;
}
}
public void Receive(ApplicationThemeChanged message) public void Receive(ApplicationThemeChanged message)
{ {
ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark; ViewModel.IsDarkWebviewRenderer = message.IsUnderlyingThemeDark;
@@ -3,9 +3,11 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract" xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract"
xmlns:calendarViewModels="using:Wino.Calendar.ViewModels"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:controls1="using:Microsoft.UI.Xaml.Controls" xmlns:controls1="using:Microsoft.UI.Xaml.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="using:Wino.Calendar.ViewModels.Data"
xmlns:domain="using:Wino.Core.Domain" xmlns:domain="using:Wino.Core.Domain"
xmlns:entities="using:Wino.Core.Domain.Entities.Shared" xmlns:entities="using:Wino.Core.Domain.Entities.Shared"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -236,9 +238,7 @@
</controls:SettingsCard.Content> </controls:SettingsCard.Content>
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard <controls:SettingsCard Description="{x:Bind domain:Translator.CalendarSettings_DefaultSnoozeDuration_Description}" Header="{x:Bind domain:Translator.CalendarSettings_DefaultSnoozeDuration_Header}">
Description="{x:Bind domain:Translator.CalendarSettings_DefaultSnoozeDuration_Description}"
Header="{x:Bind domain:Translator.CalendarSettings_DefaultSnoozeDuration_Header}">
<controls:SettingsCard.HeaderIcon> <controls:SettingsCard.HeaderIcon>
<PathIcon Data="F1 M 10 1.25 C 10.456706 1.25 10.889486 1.337565 11.298339 1.512695 C 11.707192 1.687826 12.072591 1.927409 12.394531 2.231445 C 12.716471 2.535482 12.97656 2.892253 13.173828 3.301758 C 13.371096 3.711263 13.481771 4.146484 13.505859 4.607422 L 13.505859 5 C 13.969401 5.028646 14.386068 5.16276 14.755859 5.402344 C 15.125651 5.641927 15.414713 5.947917 15.623047 6.320312 C 15.83138 6.692709 15.9349 7.096355 15.933594 7.53125 L 15.933594 8.75 L 16.25 8.75 C 16.822917 8.75 17.317057 8.95638 17.732422 9.369141 C 18.147787 9.781901 18.357422 10.273437 18.359375 10.84375 L 18.359375 16.40625 C 18.359375 16.979167 18.153971 17.473308 17.743164 17.888672 C 17.332357 18.304037 16.839844 18.511719 16.265625 18.511719 L 3.734375 18.511719 C 3.164062 18.511719 2.671549 18.304037 2.256836 17.888672 C 1.842122 17.473308 1.634114 16.979167 1.630859 16.40625 L 1.630859 10.84375 C 1.630859 10.273437 1.837565 9.781901 2.250977 9.369141 C 2.664388 8.95638 3.156901 8.75 3.728516 8.75 L 4.0625 8.75 L 4.0625 7.53125 C 4.0625 7.09375 4.166341 6.689453 4.374023 6.314453 C 4.581706 5.939453 4.86914 5.632486 5.236328 5.393555 C 5.603516 5.154623 6.019532 5.021485 6.484375 4.994141 L 6.484375 4.607422 C 6.506511 4.148438 6.617838 3.714518 6.818359 3.305664 C 7.01888 2.896811 7.282877 2.539063 7.610352 2.232422 C 7.937826 1.92578 8.308268 1.685222 8.721679 1.510742 C 9.135091 1.336264 9.56575 1.249024 10.013672 1.249024 Z M 10.013672 2.5 C 9.441406 2.5 8.947916 2.706381 8.533203 3.119141 C 8.118489 3.531901 7.911459 4.023437 7.912109 4.59375 L 7.912109 6.25 L 12.089844 6.25 L 12.089844 4.59375 C 12.089844 4.023438 11.882161 3.531902 11.466797 3.119141 C 11.051433 2.706382 10.557292 2.5 9.984375 2.5 Z M 5.3125 7.53125 L 5.3125 8.75 L 14.6875 8.75 L 14.6875 7.53125 C 14.6875 7.303385 14.605144 7.107747 14.440429 6.944336 C 14.275714 6.780925 14.080729 6.69987 13.855469 6.703125 L 6.142578 6.703125 C 5.914713 6.703125 5.71875 6.785482 5.552734 6.950195 C 5.386719 7.114909 5.30339 7.310547 5.300781 7.537109 Z M 16.25 10 L 3.75 10 C 3.540365 10 3.361328 10.071615 3.212891 10.214844 C 3.064453 10.358073 2.989258 10.534505 2.986328 10.744141 L 2.986328 16.40625 C 2.986328 16.618489 3.058595 16.797526 3.203125 16.943359 C 3.347656 17.089193 3.526693 17.161458 3.740234 17.15625 L 16.25 17.15625 C 16.458333 17.15625 16.636067 17.082683 16.783203 16.935547 C 16.930339 16.788411 17.003906 16.609375 17.003906 16.398437 L 17.003906 10.742187 C 17.003906 10.536459 16.932942 10.359049 16.791016 10.209961 C 16.64909 10.060873 16.469401 9.986328 16.257812 9.986328 Z M 10 11.5625 C 10.227865 11.5625 10.423502 11.644856 10.586914 11.809571 C 10.750325 11.974285 10.83138 12.16862 10.830078 12.393555 L 10.830078 13.427734 L 11.855469 13.427734 C 12.080404 13.427734 12.273437 13.509115 12.434571 13.671875 C 12.595704 13.834636 12.675781 14.026693 12.675781 14.248047 C 12.675781 14.46224 12.596354 14.653321 12.4375 14.813477 C 12.278646 14.973633 12.089192 15.053711 11.869141 15.053711 L 10.830078 15.053711 L 10.830078 16.074219 C 10.830078 16.296224 10.748372 16.486981 10.584961 16.646484 C 10.42155 16.805989 10.229818 16.885742 10.005859 16.885742 C 9.781901 16.885742 9.590495 16.806315 9.431641 16.647461 C 9.272786 16.488607 9.193359 16.296875 9.193359 16.072266 L 9.193359 15.053711 L 8.15625 15.053711 C 7.931314 15.053711 7.738934 14.972331 7.579102 14.80957 C 7.419269 14.646809 7.339192 14.454753 7.338867 14.233399 C 7.338867 14.008464 7.419921 13.815755 7.582031 13.655274 C 7.744141 13.494793 7.935872 13.414551 8.157227 13.414551 L 9.193359 13.414551 L 9.193359 12.394531 C 9.193359 12.166667 9.275715 11.971029 9.440429 11.807618 C 9.605144 11.644206 9.799479 11.562825 10.023437 11.5625 Z" /> <PathIcon Data="F1 M 10 1.25 C 10.456706 1.25 10.889486 1.337565 11.298339 1.512695 C 11.707192 1.687826 12.072591 1.927409 12.394531 2.231445 C 12.716471 2.535482 12.97656 2.892253 13.173828 3.301758 C 13.371096 3.711263 13.481771 4.146484 13.505859 4.607422 L 13.505859 5 C 13.969401 5.028646 14.386068 5.16276 14.755859 5.402344 C 15.125651 5.641927 15.414713 5.947917 15.623047 6.320312 C 15.83138 6.692709 15.9349 7.096355 15.933594 7.53125 L 15.933594 8.75 L 16.25 8.75 C 16.822917 8.75 17.317057 8.95638 17.732422 9.369141 C 18.147787 9.781901 18.357422 10.273437 18.359375 10.84375 L 18.359375 16.40625 C 18.359375 16.979167 18.153971 17.473308 17.743164 17.888672 C 17.332357 18.304037 16.839844 18.511719 16.265625 18.511719 L 3.734375 18.511719 C 3.164062 18.511719 2.671549 18.304037 2.256836 17.888672 C 1.842122 17.473308 1.634114 16.979167 1.630859 16.40625 L 1.630859 10.84375 C 1.630859 10.273437 1.837565 9.781901 2.250977 9.369141 C 2.664388 8.95638 3.156901 8.75 3.728516 8.75 L 4.0625 8.75 L 4.0625 7.53125 C 4.0625 7.09375 4.166341 6.689453 4.374023 6.314453 C 4.581706 5.939453 4.86914 5.632486 5.236328 5.393555 C 5.603516 5.154623 6.019532 5.021485 6.484375 4.994141 L 6.484375 4.607422 C 6.506511 4.148438 6.617838 3.714518 6.818359 3.305664 C 7.01888 2.896811 7.282877 2.539063 7.610352 2.232422 C 7.937826 1.92578 8.308268 1.685222 8.721679 1.510742 C 9.135091 1.336264 9.56575 1.249024 10.013672 1.249024 Z M 10.013672 2.5 C 9.441406 2.5 8.947916 2.706381 8.533203 3.119141 C 8.118489 3.531901 7.911459 4.023437 7.912109 4.59375 L 7.912109 6.25 L 12.089844 6.25 L 12.089844 4.59375 C 12.089844 4.023438 11.882161 3.531902 11.466797 3.119141 C 11.051433 2.706382 10.557292 2.5 9.984375 2.5 Z M 5.3125 7.53125 L 5.3125 8.75 L 14.6875 8.75 L 14.6875 7.53125 C 14.6875 7.303385 14.605144 7.107747 14.440429 6.944336 C 14.275714 6.780925 14.080729 6.69987 13.855469 6.703125 L 6.142578 6.703125 C 5.914713 6.703125 5.71875 6.785482 5.552734 6.950195 C 5.386719 7.114909 5.30339 7.310547 5.300781 7.537109 Z M 16.25 10 L 3.75 10 C 3.540365 10 3.361328 10.071615 3.212891 10.214844 C 3.064453 10.358073 2.989258 10.534505 2.986328 10.744141 L 2.986328 16.40625 C 2.986328 16.618489 3.058595 16.797526 3.203125 16.943359 C 3.347656 17.089193 3.526693 17.161458 3.740234 17.15625 L 16.25 17.15625 C 16.458333 17.15625 16.636067 17.082683 16.783203 16.935547 C 16.930339 16.788411 17.003906 16.609375 17.003906 16.398437 L 17.003906 10.742187 C 17.003906 10.536459 16.932942 10.359049 16.791016 10.209961 C 16.64909 10.060873 16.469401 9.986328 16.257812 9.986328 Z M 10 11.5625 C 10.227865 11.5625 10.423502 11.644856 10.586914 11.809571 C 10.750325 11.974285 10.83138 12.16862 10.830078 12.393555 L 10.830078 13.427734 L 11.855469 13.427734 C 12.080404 13.427734 12.273437 13.509115 12.434571 13.671875 C 12.595704 13.834636 12.675781 14.026693 12.675781 14.248047 C 12.675781 14.46224 12.596354 14.653321 12.4375 14.813477 C 12.278646 14.973633 12.089192 15.053711 11.869141 15.053711 L 10.830078 15.053711 L 10.830078 16.074219 C 10.830078 16.296224 10.748372 16.486981 10.584961 16.646484 C 10.42155 16.805989 10.229818 16.885742 10.005859 16.885742 C 9.781901 16.885742 9.590495 16.806315 9.431641 16.647461 C 9.272786 16.488607 9.193359 16.296875 9.193359 16.072266 L 9.193359 15.053711 L 8.15625 15.053711 C 7.931314 15.053711 7.738934 14.972331 7.579102 14.80957 C 7.419269 14.646809 7.339192 14.454753 7.338867 14.233399 C 7.338867 14.008464 7.419921 13.815755 7.582031 13.655274 C 7.744141 13.494793 7.935872 13.414551 8.157227 13.414551 L 9.193359 13.414551 L 9.193359 12.394531 C 9.193359 12.166667 9.275715 11.971029 9.440429 11.807618 C 9.605144 11.644206 9.799479 11.562825 10.023437 11.5625 Z" />
</controls:SettingsCard.HeaderIcon> </controls:SettingsCard.HeaderIcon>
@@ -246,6 +246,40 @@
<ComboBox ItemsSource="{x:Bind ViewModel.SnoozeOptions, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.SelectedDefaultSnoozeIndex, Mode=TwoWay}" /> <ComboBox ItemsSource="{x:Bind ViewModel.SnoozeOptions, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.SelectedDefaultSnoozeIndex, Mode=TwoWay}" />
</controls:SettingsCard.Content> </controls:SettingsCard.Content>
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard Description="{x:Bind domain:Translator.CalendarSettings_NewEventBehavior_Description}" Header="{x:Bind domain:Translator.CalendarSettings_NewEventBehavior_Header}">
<controls:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE163;" />
</controls:SettingsCard.HeaderIcon>
<controls:SettingsCard.Content>
<StackPanel Spacing="12">
<ComboBox ItemsSource="{x:Bind ViewModel.NewEventBehaviorOptions, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedNewEventBehaviorOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:CalendarNewEventBehaviorOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox
ItemsSource="{x:Bind ViewModel.AvailableNewEventCalendars, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedNewEventCalendar, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.ShouldShowSpecificNewEventCalendar, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="data:AccountCalendarViewModel">
<StackPanel Orientation="Vertical" Spacing="2">
<TextBlock Text="{x:Bind Name}" />
<TextBlock
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Account.Address}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</controls:SettingsCard.Content>
</controls:SettingsCard>
</StackPanel> </StackPanel>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ClockIdentifierStates"> <VisualStateGroup x:Name="ClockIdentifierStates">
@@ -179,9 +179,14 @@
<ComboBox <ComboBox
Width="150" Width="150"
VerticalAlignment="Center" VerticalAlignment="Center"
DisplayMemberPath="DisplayText"
ItemsSource="{x:Bind ViewModel.ShowAsOptions}" ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}" /> SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="calendarViewModels:ShowAsOption">
<TextBlock Text="{x:Bind DisplayText}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel> </StackPanel>
<!-- Reminder --> <!-- Reminder -->
+30
View File
@@ -54,11 +54,41 @@ public class CalendarService : BaseDatabaseService, ICalendarService
public async Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar) public async Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar)
{ {
if (accountCalendar.IsPrimary)
{
await Connection.ExecuteAsync(
"UPDATE AccountCalendar SET IsPrimary = 0 WHERE AccountId = ? AND Id != ?",
accountCalendar.AccountId,
accountCalendar.Id);
}
await Connection.UpdateAsync(accountCalendar, typeof(AccountCalendar)); await Connection.UpdateAsync(accountCalendar, typeof(AccountCalendar));
WeakReferenceMessenger.Default.Send(new CalendarListUpdated(accountCalendar)); WeakReferenceMessenger.Default.Send(new CalendarListUpdated(accountCalendar));
} }
public async Task SetPrimaryCalendarAsync(Guid accountId, Guid accountCalendarId)
{
await Connection.RunInTransactionAsync(connection =>
{
connection.Execute(
"UPDATE AccountCalendar SET IsPrimary = 0 WHERE AccountId = ?",
accountId);
connection.Execute(
"UPDATE AccountCalendar SET IsPrimary = 1 WHERE AccountId = ? AND Id = ?",
accountId,
accountCalendarId);
});
var calendars = await GetAccountCalendarsAsync(accountId).ConfigureAwait(false);
foreach (var calendar in calendars)
{
WeakReferenceMessenger.Default.Send(new CalendarListUpdated(calendar));
}
}
public async Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar) public async Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar)
{ {
await Connection.ExecuteAsync( await Connection.ExecuteAsync(