using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Text; using System.Text.Json; using CommunityToolkit.Mvvm.ComponentModel; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Calendar; using Wino.Core.Domain.Models.Reader; using Wino.Core.Domain.Translations; using Wino.Services; namespace Wino.Mail.WinUI.Services; public class PreferencesService(IConfigurationService configurationService) : ObservableObject, IPreferencesService { private readonly IConfigurationService _configurationService = configurationService; public event EventHandler? PreferenceChanged; protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); PreferenceChanged?.Invoke(this, e.PropertyName ?? string.Empty); } private void SaveProperty(string propertyName, object? value) => _configurationService.Set(propertyName, value ?? string.Empty); private void SetPropertyAndSave(string propertyName, object? value) { _configurationService.Set(propertyName, value ?? string.Empty); OnPropertyChanged(propertyName); Debug.WriteLine($"PreferencesService -> {propertyName}:{value?.ToString()}"); } public MailRenderingOptions GetRenderingOptions() => new MailRenderingOptions() { LoadImages = RenderImages, LoadStyles = RenderStyles, RenderPlaintextLinks = RenderPlaintextLinks }; public string ExportPreferences() { var settings = new Dictionary(StringComparer.Ordinal); foreach (var property in GetSyncablePreferenceProperties()) { settings[property.Name] = property.GetValue(this); } using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream)) { writer.WriteStartObject(); foreach (var setting in settings) { WritePreferenceValue(writer, setting.Key, setting.Value); } writer.WriteEndObject(); } return Encoding.UTF8.GetString(stream.ToArray()); } public (int appliedCount, int failedCount) ImportPreferences(string settingsJson) { using var document = JsonDocument.Parse(settingsJson); var rootElement = document.RootElement; var appliedCount = 0; var failedCount = 0; foreach (var property in GetSyncablePreferenceProperties()) { if (!rootElement.TryGetProperty(property.Name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined) { continue; } try { property.SetValue(this, ReadPreferenceValue(property.PropertyType, value)); appliedCount++; } catch (Exception) { failedCount++; } } return (appliedCount, failedCount); } public MailListDisplayMode MailItemDisplayMode { get => _configurationService.Get(nameof(MailItemDisplayMode), MailListDisplayMode.Spacious); set => SetPropertyAndSave(nameof(MailItemDisplayMode), value); } public bool IsHardDeleteProtectionEnabled { get => _configurationService.Get(nameof(IsHardDeleteProtectionEnabled), true); set => SetPropertyAndSave(nameof(IsHardDeleteProtectionEnabled), value); } public bool IsThreadingEnabled { get => _configurationService.Get(nameof(IsThreadingEnabled), true); set => SetPropertyAndSave(nameof(IsThreadingEnabled), value); } public bool IsMailListActionBarEnabled { get => _configurationService.Get(nameof(IsMailListActionBarEnabled), false); set => SetPropertyAndSave(nameof(IsMailListActionBarEnabled), value); } public bool IsShowActionLabelsEnabled { get => _configurationService.Get(nameof(IsShowActionLabelsEnabled), true); set => SetPropertyAndSave(nameof(IsShowActionLabelsEnabled), value); } public bool IsShowSenderPicturesEnabled { get => _configurationService.Get(nameof(IsShowSenderPicturesEnabled), true); set => SetPropertyAndSave(nameof(IsShowSenderPicturesEnabled), value); } public bool IsShowPreviewEnabled { get => _configurationService.Get(nameof(IsShowPreviewEnabled), true); set => SetPropertyAndSave(nameof(IsShowPreviewEnabled), value); } public bool RenderStyles { get => _configurationService.Get(nameof(RenderStyles), true); set => SetPropertyAndSave(nameof(RenderStyles), value); } public bool RenderPlaintextLinks { get => _configurationService.Get(nameof(RenderPlaintextLinks), true); set => SetPropertyAndSave(nameof(RenderPlaintextLinks), value); } public bool RenderImages { get => _configurationService.Get(nameof(RenderImages), true); set => SetPropertyAndSave(nameof(RenderImages), value); } public bool Prefer24HourTimeFormat { get => _configurationService.Get(nameof(Prefer24HourTimeFormat), false); set => SetPropertyAndSave(nameof(Prefer24HourTimeFormat), value); } public MailMarkAsOption MarkAsPreference { get => _configurationService.Get(nameof(MarkAsPreference), MailMarkAsOption.WhenSelected); set => SetPropertyAndSave(nameof(MarkAsPreference), value); } public int MarkAsDelay { get => _configurationService.Get(nameof(MarkAsDelay), 5); set => SetPropertyAndSave(nameof(MarkAsDelay), value); } public MailOperation RightSwipeOperation { get => _configurationService.Get(nameof(RightSwipeOperation), MailOperation.MarkAsRead); set => SetPropertyAndSave(nameof(RightSwipeOperation), value); } public MailOperation LeftSwipeOperation { get => _configurationService.Get(nameof(LeftSwipeOperation), MailOperation.SoftDelete); set => SetPropertyAndSave(nameof(LeftSwipeOperation), value); } public bool IsHoverActionsEnabled { get => _configurationService.Get(nameof(IsHoverActionsEnabled), true); set => SetPropertyAndSave(nameof(IsHoverActionsEnabled), value); } public MailOperation LeftHoverAction { get => _configurationService.Get(nameof(LeftHoverAction), MailOperation.Archive); set => SetPropertyAndSave(nameof(LeftHoverAction), value); } public MailOperation CenterHoverAction { get => _configurationService.Get(nameof(CenterHoverAction), MailOperation.SoftDelete); set => SetPropertyAndSave(nameof(CenterHoverAction), value); } public MailOperation RightHoverAction { get => _configurationService.Get(nameof(RightHoverAction), MailOperation.SetFlag); set => SetPropertyAndSave(nameof(RightHoverAction), value); } public bool IsLoggingEnabled { get => _configurationService.Get(nameof(IsLoggingEnabled), true); set => SetPropertyAndSave(nameof(IsLoggingEnabled), value); } public bool IsMailkitProtocolLoggerEnabled { get => _configurationService.Get(nameof(IsMailkitProtocolLoggerEnabled), false); set => SetPropertyAndSave(nameof(IsMailkitProtocolLoggerEnabled), value); } public bool IsGravatarEnabled { get => _configurationService.Get(nameof(IsGravatarEnabled), true); set => SetPropertyAndSave(nameof(IsGravatarEnabled), value); } public bool IsFaviconEnabled { get => _configurationService.Get(nameof(IsFaviconEnabled), true); set => SetPropertyAndSave(nameof(IsFaviconEnabled), value); } public Guid? StartupEntityId { get => _configurationService.Get(nameof(StartupEntityId), null); set => SaveProperty(propertyName: nameof(StartupEntityId), value); } public AppLanguage CurrentLanguage { get => _configurationService.Get(nameof(CurrentLanguage), TranslationService.DefaultAppLanguage); set => SaveProperty(propertyName: nameof(CurrentLanguage), value); } public string ReaderFont { get => _configurationService.Get(nameof(ReaderFont), "Calibri"); set => SaveProperty(propertyName: nameof(ReaderFont), value); } public int ReaderFontSize { get => _configurationService.Get(nameof(ReaderFontSize), 14); set => SaveProperty(propertyName: nameof(ReaderFontSize), value); } public string ComposerFont { get => _configurationService.Get(nameof(ComposerFont), "Calibri"); set => SaveProperty(propertyName: nameof(ComposerFont), value); } public int ComposerFontSize { get => _configurationService.Get(nameof(ComposerFontSize), 14); set => SaveProperty(propertyName: nameof(ComposerFontSize), value); } public bool IsNavigationPaneOpened { get => _configurationService.Get(nameof(IsNavigationPaneOpened), true); set => SetPropertyAndSave(propertyName: nameof(IsNavigationPaneOpened), value); } public bool AutoSelectNextItem { get => _configurationService.Get(nameof(AutoSelectNextItem), true); set => SaveProperty(propertyName: nameof(AutoSelectNextItem), value); } public string DiagnosticId { get => _configurationService.Get(nameof(DiagnosticId), Guid.NewGuid().ToString()); set => SaveProperty(propertyName: nameof(DiagnosticId), value); } public SearchMode DefaultSearchMode { get => _configurationService.Get(nameof(DefaultSearchMode), SearchMode.Local); set => SaveProperty(propertyName: nameof(DefaultSearchMode), value); } public DayOfWeek FirstDayOfWeek { get => _configurationService.Get(nameof(FirstDayOfWeek), DayOfWeek.Monday); set => SaveProperty(propertyName: nameof(FirstDayOfWeek), value); } public double HourHeight { get => _configurationService.Get(nameof(HourHeight), 60.0); set => SaveProperty(propertyName: nameof(HourHeight), value); } public bool IsWorkingHoursEnabled { get => _configurationService.Get(nameof(IsWorkingHoursEnabled), true); set => SaveProperty(propertyName: nameof(IsWorkingHoursEnabled), value); } public string CalendarTimedDayHeaderDateFormat { get => _configurationService.Get(nameof(CalendarTimedDayHeaderDateFormat), "ddd dd"); set => SaveProperty(propertyName: nameof(CalendarTimedDayHeaderDateFormat), string.IsNullOrWhiteSpace(value) ? "ddd dd" : value.Trim()); } public TimeSpan WorkingHourStart { get => _configurationService.Get(nameof(WorkingHourStart), new TimeSpan(9, 0, 0)); set => SaveProperty(propertyName: nameof(WorkingHourStart), value); } public TimeSpan WorkingHourEnd { get => _configurationService.Get(nameof(WorkingHourEnd), new TimeSpan(18, 0, 0)); set => SaveProperty(propertyName: nameof(WorkingHourEnd), value); } public DayOfWeek WorkingDayStart { get => _configurationService.Get(nameof(WorkingDayStart), DayOfWeek.Monday); set => SaveProperty(propertyName: nameof(WorkingDayStart), value); } public DayOfWeek WorkingDayEnd { get => _configurationService.Get(nameof(WorkingDayEnd), DayOfWeek.Friday); set => SaveProperty(propertyName: nameof(WorkingDayEnd), value); } public long DefaultReminderDurationInSeconds { get => _configurationService.Get(nameof(DefaultReminderDurationInSeconds), 900L); // Default: 15 minutes (900 seconds) set => SaveProperty(propertyName: nameof(DefaultReminderDurationInSeconds), value); } public int DefaultSnoozeDurationInMinutes { get => _configurationService.Get(nameof(DefaultSnoozeDurationInMinutes), 5); 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(nameof(DefaultNewEventCalendarId), null); set => SetPropertyAndSave(nameof(DefaultNewEventCalendarId), value); } public int EmailSyncIntervalMinutes { get => _configurationService.Get(nameof(EmailSyncIntervalMinutes), 3); set => SetPropertyAndSave(nameof(EmailSyncIntervalMinutes), value); } public bool IsStoreUpdateNotificationsEnabled { get => _configurationService.Get(nameof(IsStoreUpdateNotificationsEnabled), true); set => SetPropertyAndSave(nameof(IsStoreUpdateNotificationsEnabled), value); } public bool IsWinoAccountButtonHidden { get => _configurationService.Get(nameof(IsWinoAccountButtonHidden), false); set => SetPropertyAndSave(nameof(IsWinoAccountButtonHidden), value); } public string AiDefaultTranslationLanguageCode { get => _configurationService.Get(nameof(AiDefaultTranslationLanguageCode), "en-US"); set => SetPropertyAndSave(nameof(AiDefaultTranslationLanguageCode), string.IsNullOrWhiteSpace(value) ? "en-US" : value.Trim()); } public string AiSummarizeLanguageCode { get => _configurationService.Get(nameof(AiSummarizeLanguageCode), "en-US"); set => SetPropertyAndSave(nameof(AiSummarizeLanguageCode), string.IsNullOrWhiteSpace(value) ? "en-US" : value.Trim()); } public string AiSummarySavePath { get => _configurationService.Get(nameof(AiSummarySavePath), string.Empty); set => SetPropertyAndSave(nameof(AiSummarySavePath), value?.Trim() ?? string.Empty); } public WinoApplicationMode DefaultApplicationMode { get { var configuredMode = _configurationService.Get(nameof(DefaultApplicationMode), WinoApplicationMode.Mail); return Enum.IsDefined(typeof(WinoApplicationMode), configuredMode) ? configuredMode : WinoApplicationMode.Mail; } set => SaveProperty(propertyName: nameof(DefaultApplicationMode), value); } public CalendarSettings GetCurrentCalendarSettings() { var workingDays = GetDaysBetween(WorkingDayStart, WorkingDayEnd); return new CalendarSettings(FirstDayOfWeek, workingDays, IsWorkingHoursEnabled, WorkingDayStart, WorkingDayEnd, WorkingHourStart, WorkingHourEnd, HourHeight, Prefer24HourTimeFormat ? DayHeaderDisplayType.TwentyFourHour : DayHeaderDisplayType.TwelveHour, new CultureInfo(WinoTranslationDictionary.GetLanguageFileNameRelativePath(CurrentLanguage)), CalendarTimedDayHeaderDateFormat); } private List GetDaysBetween(DayOfWeek startDay, DayOfWeek endDay) { var daysOfWeek = new List(); int currentDay = (int)startDay; int endDayInt = (int)endDay; // If endDay is before startDay in the week, wrap around if (endDayInt < currentDay) { endDayInt += 7; } // Collect days from startDay to endDay while (currentDay <= endDayInt) { daysOfWeek.Add((DayOfWeek)(currentDay % 7)); currentDay++; } return daysOfWeek; } private static void WritePreferenceValue(Utf8JsonWriter writer, string propertyName, object? value) { if (value == null) { writer.WriteNull(propertyName); return; } switch (value) { case string stringValue: writer.WriteString(propertyName, stringValue); return; case bool boolValue: writer.WriteBoolean(propertyName, boolValue); return; case int intValue: writer.WriteNumber(propertyName, intValue); return; case long longValue: writer.WriteNumber(propertyName, longValue); return; case double doubleValue: writer.WriteNumber(propertyName, doubleValue); return; case float floatValue: writer.WriteNumber(propertyName, floatValue); return; case Guid guidValue: writer.WriteString(propertyName, guidValue); return; case TimeSpan timeSpanValue: writer.WriteString(propertyName, timeSpanValue.ToString("c", CultureInfo.InvariantCulture)); return; } var valueType = Nullable.GetUnderlyingType(value.GetType()) ?? value.GetType(); if (valueType.IsEnum) { writer.WriteString(propertyName, value.ToString()); return; } writer.WriteString(propertyName, Convert.ToString(value, CultureInfo.InvariantCulture)); } private static object? ReadPreferenceValue(Type propertyType, JsonElement value) { var targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; if (value.ValueKind == JsonValueKind.Null) { return null; } if (targetType == typeof(string)) { return value.GetString() ?? string.Empty; } if (targetType == typeof(bool)) { return value.GetBoolean(); } if (targetType == typeof(int)) { return value.GetInt32(); } if (targetType == typeof(long)) { return value.GetInt64(); } if (targetType == typeof(double)) { return value.GetDouble(); } if (targetType == typeof(float)) { return value.GetSingle(); } if (targetType == typeof(Guid)) { return Guid.Parse(value.GetString() ?? string.Empty); } if (targetType == typeof(TimeSpan)) { return TimeSpan.Parse(value.GetString() ?? string.Empty, CultureInfo.InvariantCulture); } if (targetType.IsEnum) { return Enum.Parse(targetType, value.GetString() ?? string.Empty, true); } return Convert.ChangeType(value.GetString(), targetType, CultureInfo.InvariantCulture); } private static IEnumerable GetSyncablePreferenceProperties() { foreach (var property in typeof(IPreferencesService).GetProperties(BindingFlags.Instance | BindingFlags.Public)) { if (!property.CanRead || !property.CanWrite || property.GetIndexParameters().Length > 0) { continue; } yield return property; } } }