Merged feature/vNext. Initial commit for Wino Mail 2.0
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
namespace Wino.Core.Domain;
|
||||
|
||||
public static class AppUrls
|
||||
{
|
||||
public const string Website = "https://www.winomail.app";
|
||||
public const string Discord = "https://discord.gg/windows-apps-hub-714581497222398064";
|
||||
public const string GitHub = "https://github.com/bkaankose/Wino-Mail/";
|
||||
public const string PrivacyPolicy = "https://www.winomail.app/support/privacy";
|
||||
public const string Paypal = "https://paypal.me/bkaankose?country.x=PL&locale.x=en_US";
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Wino.Core.Domain.Models.Updates;
|
||||
|
||||
namespace Wino.Core.Domain;
|
||||
|
||||
@@ -8,4 +9,6 @@ namespace Wino.Core.Domain;
|
||||
[JsonSerializable(typeof(int))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(bool))]
|
||||
[JsonSerializable(typeof(UpdateNotes))]
|
||||
[JsonSerializable(typeof(List<UpdateNoteSection>))]
|
||||
public partial class BasicTypesJsonContext : JsonSerializerContext;
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain;
|
||||
|
||||
public static class CalendarRecurrenceSummaryFormatter
|
||||
{
|
||||
private static readonly DayOfWeek[] OrderedDays =
|
||||
[
|
||||
DayOfWeek.Monday,
|
||||
DayOfWeek.Tuesday,
|
||||
DayOfWeek.Wednesday,
|
||||
DayOfWeek.Thursday,
|
||||
DayOfWeek.Friday,
|
||||
DayOfWeek.Saturday,
|
||||
DayOfWeek.Sunday
|
||||
];
|
||||
|
||||
public static string BuildSummary(
|
||||
bool isRecurring,
|
||||
DateTimeOffset effectiveStart,
|
||||
DateTimeOffset effectiveEnd,
|
||||
bool isAllDay,
|
||||
CalendarSettings settings,
|
||||
int interval,
|
||||
CalendarItemRecurrenceFrequency frequency,
|
||||
IReadOnlyCollection<DayOfWeek> daysOfWeek,
|
||||
DateTimeOffset? recurrenceEndDate)
|
||||
{
|
||||
var culture = settings?.CultureInfo ?? CultureInfo.CurrentCulture;
|
||||
var timeSummary = isAllDay
|
||||
? Translator.CalendarItemAllDay
|
||||
: string.Format(
|
||||
culture,
|
||||
Translator.CalendarEventCompose_TimeRangeSummary,
|
||||
effectiveStart.ToString(settings?.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "HH:mm" : "h:mm tt", culture),
|
||||
effectiveEnd.ToString(settings?.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "HH:mm" : "h:mm tt", culture));
|
||||
|
||||
if (!isRecurring)
|
||||
{
|
||||
return string.Format(
|
||||
culture,
|
||||
Translator.CalendarEventCompose_SingleOccurrenceSummary,
|
||||
effectiveStart.ToString("dddd yyyy-MM-dd", culture),
|
||||
timeSummary);
|
||||
}
|
||||
|
||||
var normalizedDays = NormalizeDays(daysOfWeek);
|
||||
var isEveryDay = (frequency == CalendarItemRecurrenceFrequency.Daily && interval == 1) ||
|
||||
(frequency == CalendarItemRecurrenceFrequency.Weekly && interval == 1 && normalizedDays.Count == 7);
|
||||
|
||||
var cadenceSummary = isEveryDay
|
||||
? $"{Translator.CalendarEventCompose_Every} {Translator.CalendarEventCompose_FrequencyDay}"
|
||||
: interval == 1
|
||||
? $"{Translator.CalendarEventCompose_Every} {GetSingularFrequencyLabel(frequency)}"
|
||||
: $"{Translator.CalendarEventCompose_Every} {interval.ToString(culture)} {GetPluralFrequencyLabel(frequency)}";
|
||||
|
||||
var weekdaySummary = string.Empty;
|
||||
if (frequency == CalendarItemRecurrenceFrequency.Weekly && normalizedDays.Count > 0 && normalizedDays.Count < 7)
|
||||
{
|
||||
weekdaySummary = string.Format(
|
||||
culture,
|
||||
Translator.CalendarEventCompose_WeekdaySummary,
|
||||
string.Join(", ", normalizedDays.Select(day => culture.DateTimeFormat.GetDayName(day))));
|
||||
}
|
||||
|
||||
var untilSummary = recurrenceEndDate.HasValue
|
||||
? string.Format(
|
||||
culture,
|
||||
Translator.CalendarEventCompose_UntilSummary,
|
||||
recurrenceEndDate.Value.ToString("ddd yyyy-MM-dd", culture))
|
||||
: string.Empty;
|
||||
|
||||
return string.Format(
|
||||
culture,
|
||||
Translator.CalendarEventCompose_RecurringSummarySmart,
|
||||
cadenceSummary,
|
||||
weekdaySummary,
|
||||
timeSummary,
|
||||
effectiveStart.ToString("dddd yyyy-MM-dd", culture),
|
||||
untilSummary).Trim();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<DayOfWeek> NormalizeDays(IReadOnlyCollection<DayOfWeek> daysOfWeek)
|
||||
{
|
||||
if (daysOfWeek == null || daysOfWeek.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return daysOfWeek
|
||||
.Distinct()
|
||||
.OrderBy(day => Array.IndexOf(OrderedDays, day))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static string GetSingularFrequencyLabel(CalendarItemRecurrenceFrequency frequency)
|
||||
{
|
||||
return frequency switch
|
||||
{
|
||||
CalendarItemRecurrenceFrequency.Daily => Translator.CalendarEventCompose_FrequencyDay,
|
||||
CalendarItemRecurrenceFrequency.Weekly => Translator.CalendarEventCompose_FrequencyWeek,
|
||||
CalendarItemRecurrenceFrequency.Monthly => Translator.CalendarEventCompose_FrequencyMonth,
|
||||
CalendarItemRecurrenceFrequency.Yearly => Translator.CalendarEventCompose_FrequencyYear,
|
||||
_ => Translator.CalendarEventCompose_FrequencyWeek
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetPluralFrequencyLabel(CalendarItemRecurrenceFrequency frequency)
|
||||
{
|
||||
return frequency switch
|
||||
{
|
||||
CalendarItemRecurrenceFrequency.Daily => Translator.CalendarEventCompose_FrequencyDayPlural,
|
||||
CalendarItemRecurrenceFrequency.Weekly => Translator.CalendarEventCompose_FrequencyWeekPlural,
|
||||
CalendarItemRecurrenceFrequency.Monthly => Translator.CalendarEventCompose_FrequencyMonthPlural,
|
||||
CalendarItemRecurrenceFrequency.Yearly => Translator.CalendarEventCompose_FrequencyYearPlural,
|
||||
_ => Translator.CalendarEventCompose_FrequencyWeekPlural
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Wino.Core.Domain;
|
||||
|
||||
public static class CalendarReminderSnoozeOptions
|
||||
{
|
||||
private static readonly int[] SupportedSnoozeMinutes = [5, 10, 15, 30];
|
||||
|
||||
public static IReadOnlyList<int> GetSupportedSnoozeMinutes()
|
||||
=> SupportedSnoozeMinutes;
|
||||
|
||||
public static IReadOnlyList<int> GetAllowedSnoozeMinutes(long reminderDurationInSeconds, long defaultReminderDurationInSeconds)
|
||||
{
|
||||
var reminderMinutes = (int)Math.Max(0, reminderDurationInSeconds / 60);
|
||||
|
||||
if (reminderMinutes <= 0)
|
||||
return [];
|
||||
|
||||
var maxSnoozeMinutes = reminderMinutes;
|
||||
var defaultReminderMinutes = (int)Math.Max(0, defaultReminderDurationInSeconds / 60);
|
||||
|
||||
if (defaultReminderMinutes > 0)
|
||||
maxSnoozeMinutes = Math.Min(maxSnoozeMinutes, defaultReminderMinutes);
|
||||
|
||||
return SupportedSnoozeMinutes.Where(minutes => minutes <= maxSnoozeMinutes).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public class CalendarEventCollection
|
||||
{
|
||||
public event EventHandler<ICalendarItem> CalendarItemAdded;
|
||||
public event EventHandler<ICalendarItem> CalendarItemRemoved;
|
||||
public event EventHandler<ICalendarItem> CalendarItemUpdated;
|
||||
|
||||
public event EventHandler CalendarItemsCleared;
|
||||
|
||||
@@ -114,11 +115,27 @@ public class CalendarEventCollection
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCalendarItems(Func<ICalendarItem, bool> predicate)
|
||||
{
|
||||
if (predicate == null) return;
|
||||
|
||||
var itemsToRemove = _allItems.Where(predicate).ToList();
|
||||
|
||||
foreach (var item in itemsToRemove)
|
||||
{
|
||||
RemoveCalendarItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCalendarItemInternal(ObservableRangeCollection<ICalendarItem> collection, ICalendarItem calendarItem, bool create = true)
|
||||
{
|
||||
if (calendarItem is not ICalendarItemViewModel)
|
||||
if (calendarItem is not ICalendarItemViewModel viewModel)
|
||||
throw new ArgumentException("CalendarItem must be of type ICalendarItemViewModel", nameof(calendarItem));
|
||||
|
||||
// Set the displaying context for proper title calculation
|
||||
viewModel.DisplayingPeriod = Period;
|
||||
viewModel.CalendarSettings = Settings;
|
||||
|
||||
collection.Add(calendarItem);
|
||||
|
||||
if (create)
|
||||
@@ -144,6 +161,53 @@ public class CalendarEventCollection
|
||||
CalendarItemRemoved?.Invoke(this, calendarItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing calendar item in-place. If the item's type changed (all-day vs regular),
|
||||
/// it will be moved to the appropriate collection.
|
||||
/// </summary>
|
||||
/// <param name="calendarItem">The updated calendar item data.</param>
|
||||
/// <returns>True if the item was found and updated; false otherwise.</returns>
|
||||
public bool UpdateCalendarItem(CalendarItem calendarItem)
|
||||
{
|
||||
var existingItem = _allItems.FirstOrDefault(x => x.Id == calendarItem.Id);
|
||||
if (existingItem == null)
|
||||
return false;
|
||||
|
||||
// Get the collections this item is currently in (before update)
|
||||
var oldCollections = GetProperCollectionsForCalendarItem(existingItem).ToList();
|
||||
|
||||
// Update the underlying data
|
||||
if (existingItem is ICalendarItemViewModel viewModel)
|
||||
{
|
||||
viewModel.UpdateFrom(calendarItem);
|
||||
}
|
||||
|
||||
// Get the collections this item should be in (after update)
|
||||
var newCollections = GetProperCollectionsForCalendarItem(existingItem).ToList();
|
||||
|
||||
// Check if the collections changed
|
||||
var collectionsToRemoveFrom = oldCollections.Except(newCollections).ToList();
|
||||
var collectionsToAddTo = newCollections.Except(oldCollections).ToList();
|
||||
|
||||
// Remove from old collections that are no longer applicable
|
||||
foreach (var collection in collectionsToRemoveFrom)
|
||||
{
|
||||
collection.Remove(existingItem);
|
||||
}
|
||||
|
||||
// Add to new collections that are now applicable
|
||||
foreach (var collection in collectionsToAddTo)
|
||||
{
|
||||
if (!collection.Contains(existingItem))
|
||||
{
|
||||
collection.Add(existingItem);
|
||||
}
|
||||
}
|
||||
|
||||
CalendarItemUpdated?.Invoke(this, existingItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_internalAllDayEvents.Clear();
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Linq;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Collections;
|
||||
|
||||
public class DayRangeCollection : ObservableRangeCollection<DayRangeRenderModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the range of dates that are currently displayed in the collection.
|
||||
/// </summary>
|
||||
public DateRange DisplayRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Count == 0) return null;
|
||||
|
||||
var minimumLoadedDate = this[0].CalendarRenderOptions.DateRange.StartDate;
|
||||
var maximumLoadedDate = this[Count - 1].CalendarRenderOptions.DateRange.EndDate;
|
||||
|
||||
return new DateRange(minimumLoadedDate, maximumLoadedDate);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
foreach (var dayRange in this)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCalendarItem(ICalendarItem calendarItem)
|
||||
{
|
||||
foreach (var dayRange in this)
|
||||
{
|
||||
var calendarDayModel = dayRange.CalendarDays.FirstOrDefault(x => x.Period.HasInside(calendarItem.Period.Start));
|
||||
calendarDayModel?.EventsCollection.AddCalendarItem(calendarItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Wino.Core.Domain;
|
||||
namespace Wino.Core.Domain;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
@@ -12,7 +12,17 @@ public static class Constants
|
||||
|
||||
public const string ToastMailUniqueIdKey = nameof(ToastMailUniqueIdKey);
|
||||
public const string ToastActionKey = nameof(ToastActionKey);
|
||||
|
||||
public const string ToastMailAccountIdKey = nameof(ToastMailAccountIdKey);
|
||||
public const string ToastCalendarItemIdKey = nameof(ToastCalendarItemIdKey);
|
||||
public const string ToastCalendarActionKey = nameof(ToastCalendarActionKey);
|
||||
public const string ToastCalendarNavigateAction = nameof(ToastCalendarNavigateAction);
|
||||
public const string ToastCalendarSnoozeAction = nameof(ToastCalendarSnoozeAction);
|
||||
public const string ToastCalendarSnoozeDurationInputId = nameof(ToastCalendarSnoozeDurationInputId);
|
||||
public const string ToastModeKey = nameof(ToastModeKey);
|
||||
public const string ToastModeMail = nameof(ToastModeMail);
|
||||
public const string ToastModeCalendar = nameof(ToastModeCalendar);
|
||||
public const string ToastStoreUpdateActionKey = nameof(ToastStoreUpdateActionKey);
|
||||
public const string ToastStoreUpdateActionInstall = nameof(ToastStoreUpdateActionInstall);
|
||||
public const string ClientLogFile = "Client_.log";
|
||||
public const string ServerLogFile = "Server_.log";
|
||||
public const string LogArchiveFileName = "WinoLogs.zip";
|
||||
@@ -20,3 +30,4 @@ public static class Constants
|
||||
public const string WinoMailIdentiifer = nameof(WinoMailIdentiifer);
|
||||
public const string WinoCalendarIdentifier = nameof(WinoCalendarIdentifier);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
[Preserve]
|
||||
public class AccountCalendar : IAccountCalendar
|
||||
{
|
||||
[PrimaryKey]
|
||||
@@ -13,7 +16,9 @@ public class AccountCalendar : IAccountCalendar
|
||||
public string SynchronizationDeltaToken { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsPrimary { get; set; }
|
||||
public bool IsSynchronizationEnabled { get; set; } = true;
|
||||
public bool IsExtended { get; set; } = true;
|
||||
public CalendarItemShowAs DefaultShowAs { get; set; } = CalendarItemShowAs.Busy;
|
||||
|
||||
/// <summary>
|
||||
/// Unused for now.
|
||||
@@ -21,4 +26,7 @@ public class AccountCalendar : IAccountCalendar
|
||||
public string TextColorHex { get; set; }
|
||||
public string BackgroundColorHex { get; set; }
|
||||
public string TimeZone { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public MailAccount MailAccount { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
/// <summary>
|
||||
/// Represents metadata for calendar event attachments.
|
||||
/// Actual file content is downloaded on-demand.
|
||||
/// </summary>
|
||||
public class CalendarAttachment
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The calendar item this attachment belongs to.
|
||||
/// </summary>
|
||||
public Guid CalendarItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remote identifier for the attachment from the provider (Outlook, Gmail, etc.).
|
||||
/// </summary>
|
||||
public string RemoteAttachmentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File name of the attachment.
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the attachment in bytes.
|
||||
/// </summary>
|
||||
public long Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MIME content type (e.g., "application/pdf", "image/png").
|
||||
/// </summary>
|
||||
public string ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the attachment has been downloaded to local storage.
|
||||
/// </summary>
|
||||
public bool IsDownloaded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local file path where the attachment is stored (if downloaded).
|
||||
/// </summary>
|
||||
public string LocalFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the attachment was last modified.
|
||||
/// </summary>
|
||||
public DateTimeOffset LastModified { get; set; }
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
// TODO: Connect to Contact store with Wino People.
|
||||
public class CalendarEventAttendee
|
||||
{
|
||||
[PrimaryKey]
|
||||
@@ -16,4 +16,11 @@ public class CalendarEventAttendee
|
||||
public bool IsOrganizer { get; set; }
|
||||
public bool IsOptionalAttendee { get; set; }
|
||||
public string Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolved contact from the contact store. Populated at runtime via IContactService;
|
||||
/// not persisted to the database.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public AccountContact ResolvedContact { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ using System.Diagnostics;
|
||||
using Itenso.TimePeriod;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Calendar;
|
||||
|
||||
@@ -17,6 +19,14 @@ public class CalendarItem : ICalendarItem
|
||||
public string Description { get; set; }
|
||||
public string Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this item is a local preview that hasn't been synced to the server yet.
|
||||
/// When true, the item exists only in the local database without a RemoteEventId.
|
||||
/// Used to prevent duplicates when the server returns the newly created event.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public bool IsLocalPreview => string.IsNullOrEmpty(RemoteEventId);
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
public DateTime EndDate
|
||||
@@ -27,8 +37,17 @@ public class CalendarItem : ICalendarItem
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan StartDateOffset { get; set; }
|
||||
public TimeSpan EndDateOffset { get; set; }
|
||||
/// <summary>
|
||||
/// IANA timezone identifier for the start time (e.g., "America/New_York", "Europe/London").
|
||||
/// If null or empty, UTC is assumed.
|
||||
/// </summary>
|
||||
public string StartTimeZone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IANA timezone identifier for the end time (e.g., "America/New_York", "Europe/London").
|
||||
/// If null or empty, UTC is assumed.
|
||||
/// </summary>
|
||||
public string EndTimeZone { get; set; }
|
||||
|
||||
private ITimePeriod _period;
|
||||
public ITimePeriod Period
|
||||
@@ -55,9 +74,7 @@ public class CalendarItem : ICalendarItem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that are either an exceptional instance of a recurring event or occurrences.
|
||||
/// IsOccurrence is used to display occurrence instances of parent recurring events.
|
||||
/// IsOccurrence == false && IsRecurringChild == true => exceptional single instance.
|
||||
/// Events that are child instances of a recurring event (occurrences or exceptions).
|
||||
/// </summary>
|
||||
public bool IsRecurringChild
|
||||
{
|
||||
@@ -68,7 +85,7 @@ public class CalendarItem : ICalendarItem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events that are either an exceptional instance of a recurring event or occurrences.
|
||||
/// Events that are part of a recurring series (either as parent or child).
|
||||
/// </summary>
|
||||
public bool IsRecurringEvent => IsRecurringChild || IsRecurringParent;
|
||||
|
||||
@@ -121,8 +138,15 @@ public class CalendarItem : ICalendarItem
|
||||
// TODO
|
||||
public string CustomEventColorHex { get; set; }
|
||||
public string HtmlLink { get; set; }
|
||||
public DateTime? SnoozedUntil { get; set; }
|
||||
public CalendarItemStatus Status { get; set; }
|
||||
public CalendarItemVisibility Visibility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates how the event should be shown in the calendar (Free, Busy, Tentative, etc.).
|
||||
/// </summary>
|
||||
public CalendarItemShowAs ShowAs { get; set; } = CalendarItemShowAs.Busy;
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
public Guid CalendarId { get; set; }
|
||||
@@ -131,49 +155,37 @@ public class CalendarItem : ICalendarItem
|
||||
public IAccountCalendar AssignedCalendar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this item does not really exist in the database or not.
|
||||
/// These are used to display occurrence instances of parent recurring events.
|
||||
/// Id to load information related to this event (attendees, reminders, etc.).
|
||||
/// For child events, if they have their own data, use their own Id.
|
||||
/// For events that share data with their parent, return parent's Id.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public bool IsOccurrence { get; set; }
|
||||
public Guid EventTrackingId => Id;
|
||||
|
||||
/// <summary>
|
||||
/// Id to load information related to this event.
|
||||
/// Occurrences tracked by the parent recurring event if they are not exceptional instances.
|
||||
/// Recurring children here are exceptional instances. They have their own info in the database including Id.
|
||||
/// Gets the start date converted to user's local timezone for display.
|
||||
/// StartDate is stored according to StartTimeZone.
|
||||
/// </summary>
|
||||
public Guid EventTrackingId => IsOccurrence ? RecurringCalendarItemId.Value : Id;
|
||||
|
||||
public CalendarItem CreateRecurrence(DateTime startDate, double durationInSeconds)
|
||||
[Ignore]
|
||||
public DateTime LocalStartDate
|
||||
{
|
||||
// Create a copy with the new start date and duration
|
||||
|
||||
return new CalendarItem
|
||||
get
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Title = Title,
|
||||
Description = Description,
|
||||
Location = Location,
|
||||
StartDate = startDate,
|
||||
DurationInSeconds = durationInSeconds,
|
||||
Recurrence = Recurrence,
|
||||
OrganizerDisplayName = OrganizerDisplayName,
|
||||
OrganizerEmail = OrganizerEmail,
|
||||
RecurringCalendarItemId = Id,
|
||||
AssignedCalendar = AssignedCalendar,
|
||||
CalendarId = CalendarId,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
Visibility = Visibility,
|
||||
Status = Status,
|
||||
CustomEventColorHex = CustomEventColorHex,
|
||||
HtmlLink = HtmlLink,
|
||||
StartDateOffset = StartDateOffset,
|
||||
EndDateOffset = EndDateOffset,
|
||||
RemoteEventId = RemoteEventId,
|
||||
IsHidden = IsHidden,
|
||||
IsLocked = IsLocked,
|
||||
IsOccurrence = true
|
||||
};
|
||||
return this.GetLocalStartDate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the end date converted to user's local timezone for display.
|
||||
/// EndDate is calculated from StartDate and is in StartTimeZone.
|
||||
/// </summary>
|
||||
[Ignore]
|
||||
public DateTime LocalEndDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.GetLocalEndDate();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDisplayTitle(ITimePeriod displayingPeriod, CalendarSettings calendarSettings) => Period.ToString();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ public class Reminder
|
||||
public Guid Id { get; set; }
|
||||
public Guid CalendarItemId { get; set; }
|
||||
|
||||
public DateTimeOffset ReminderTime { get; set; }
|
||||
/// <summary>
|
||||
/// Duration in seconds before the event start time when the reminder should trigger.
|
||||
/// For example, 900 seconds = 15 minutes before event.
|
||||
/// </summary>
|
||||
public long DurationInSeconds { get; set; }
|
||||
public CalendarItemReminderType ReminderType { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
public class EmailTemplate
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string HtmlContent { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
@@ -59,4 +61,13 @@ public class MailAccountAlias : RemoteAccountAlias
|
||||
/// Root aliases can't be deleted.
|
||||
/// </summary>
|
||||
public bool CanDelete => !IsRootAlias;
|
||||
|
||||
public string SelectedSigningCertificateThumbprint { get; set; }
|
||||
public bool IsSmimeEncryptionEnabled { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public X509Certificate2 SelectedSigningCertificate { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public ObservableCollection<X509Certificate2> Certificates { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
@@ -11,7 +10,7 @@ namespace Wino.Core.Domain.Entities.Mail;
|
||||
/// Summary of the parsed MIME messages.
|
||||
/// Wino will do non-network operations on this table and others from the original MIME.
|
||||
/// </summary>
|
||||
public class MailCopy : IMailItem
|
||||
public class MailCopy
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique Id of the mail.
|
||||
@@ -104,6 +103,11 @@ public class MailCopy : IMailItem
|
||||
/// </summary>
|
||||
public bool HasAttachments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of mail item (regular mail, calendar invitation, calendar response, etc.).
|
||||
/// </summary>
|
||||
public MailItemType ItemType { get; set; } = MailItemType.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// Assigned draft id.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// Maps a calendar invitation mail item to a persisted calendar event.
|
||||
/// </summary>
|
||||
public class MailInvitationCalendarMapping
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MailCopy.Id value of the invitation mail.
|
||||
/// </summary>
|
||||
public string MailCopyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// iCalendar UID extracted from invitation MIME/ICS content.
|
||||
/// </summary>
|
||||
public string InvitationUid { get; set; }
|
||||
|
||||
public Guid CalendarId { get; set; }
|
||||
public Guid CalendarItemId { get; set; }
|
||||
public string CalendarRemoteEventId { get; set; }
|
||||
|
||||
public DateTime UpdatedAtUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -29,6 +29,8 @@ public class MailItemFolder : IMailItemFolder
|
||||
// For IMAP
|
||||
public uint UidValidity { get; set; }
|
||||
public long HighestModeSeq { get; set; }
|
||||
public uint HighestKnownUid { get; set; }
|
||||
public DateTime? LastUidReconcileUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Outlook shares delta changes per-folder. Gmail is for per-account.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
// TODO: This can easily evolve to Contact store, just like People app in Windows 10/11.
|
||||
// Do it.
|
||||
public class AccountContact : IEquatable<AccountContact>
|
||||
public class AccountContact : IEquatable<AccountContact>, IContactDisplayItem
|
||||
{
|
||||
/// <summary>
|
||||
/// E-mail address of the contact.
|
||||
@@ -25,9 +25,10 @@ public class AccountContact : IEquatable<AccountContact>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Base64 encoded profile image of the contact.
|
||||
/// File ID for the contact picture stored on disk.
|
||||
/// The actual file lives at {ApplicationDataFolderPath}/contacts/{ContactPictureFileId}.jpg.
|
||||
/// </summary>
|
||||
public string Base64ContactPicture { get; set; }
|
||||
public Guid? ContactPictureFileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All registered accounts have their contacts registered as root.
|
||||
@@ -36,6 +37,15 @@ public class AccountContact : IEquatable<AccountContact>
|
||||
/// </summary>
|
||||
public bool IsRootContact { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, indicates that the contact has been manually modified by the user.
|
||||
/// Contacts with this flag set to true should not be updated during synchronization.
|
||||
/// </summary>
|
||||
public bool IsOverridden { get; set; } = false;
|
||||
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(Name) ? Address : Name;
|
||||
AccountContact IContactDisplayItem.PreviewContact => this;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as AccountContact);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// A named group of contacts that can be expanded to individual addresses during mail composition.
|
||||
/// </summary>
|
||||
public class ContactGroup
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Display name of the group (e.g., "Team Alpha", "Family").</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>Optional description for the group.</summary>
|
||||
public string Description { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Associates an e-mail address with a <see cref="ContactGroup"/>.
|
||||
/// </summary>
|
||||
public class ContactGroupMember
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>Group this member belongs to.</summary>
|
||||
[Indexed]
|
||||
public Guid GroupId { get; set; }
|
||||
|
||||
/// <summary>E-mail address of the member (FK to AccountContact.Address).</summary>
|
||||
[Indexed]
|
||||
public string MemberAddress { get; set; }
|
||||
}
|
||||
@@ -30,6 +30,11 @@ public class CustomServerInformation
|
||||
public string OutgoingServerUsername { get; set; }
|
||||
public string OutgoingServerPassword { get; set; }
|
||||
|
||||
public string CalDavServiceUrl { get; set; }
|
||||
public string CalDavUsername { get; set; }
|
||||
public string CalDavPassword { get; set; }
|
||||
public ImapCalendarSupportMode CalendarSupportMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// useSSL True: SslOnConnect
|
||||
/// useSSL False: StartTlsWhenAvailable
|
||||
@@ -65,6 +70,8 @@ public class CustomServerInformation
|
||||
{ "OutgoingServerPort", OutgoingServerPort },
|
||||
{ "OutgoingServerSocketOption", OutgoingServerSocketOption.ToString() },
|
||||
{ "OutgoingAuthenticationMethod", OutgoingAuthenticationMethod.ToString() },
|
||||
{ "CalendarSupportMode", CalendarSupportMode.ToString() },
|
||||
{ "CalDavServiceUrl", CalDavServiceUrl },
|
||||
{ "ProxyServer", ProxyServer },
|
||||
{ "ProxyServerPort", ProxyServerPort }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public interface IContactDisplayItem
|
||||
{
|
||||
string DisplayName { get; }
|
||||
string Address { get; }
|
||||
AccountContact PreviewContact { get; }
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a user-defined keyboard shortcut for mail operations.
|
||||
/// </summary>
|
||||
public class KeyboardShortcut
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The application mode this shortcut applies to.
|
||||
/// </summary>
|
||||
public WinoApplicationMode Mode { get; set; } = WinoApplicationMode.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// The key combination string (e.g., "D", "Delete", "F1").
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The modifier keys for this shortcut.
|
||||
/// </summary>
|
||||
public ModifierKeys ModifierKeys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The shortcut action this shortcut triggers.
|
||||
/// </summary>
|
||||
public KeyboardShortcutAction Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this shortcut is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// When this shortcut was created.
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// User-friendly display name for the shortcut.
|
||||
/// </summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
var modifierText = string.Empty;
|
||||
if (ModifierKeys.HasFlag(ModifierKeys.Control))
|
||||
modifierText += "Ctrl+";
|
||||
if (ModifierKeys.HasFlag(ModifierKeys.Alt))
|
||||
modifierText += "Alt+";
|
||||
if (ModifierKeys.HasFlag(ModifierKeys.Shift))
|
||||
modifierText += "Shift+";
|
||||
if (ModifierKeys.HasFlag(ModifierKeys.Windows))
|
||||
modifierText += "Win+";
|
||||
|
||||
return modifierText + Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,14 @@ public class MailAccount
|
||||
/// </summary>
|
||||
public SpecialImapProvider SpecialImapProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether calendar access is granted for this account.
|
||||
/// When false, synchronizers will not process EventMessages or calendar invitations.
|
||||
/// Default is false for existing accounts to prevent scope issues.
|
||||
/// New accounts created after this feature will have this set to true.
|
||||
/// </summary>
|
||||
public bool IsCalendarAccessGranted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains the merged inbox this account belongs to.
|
||||
/// Ignored for all SQLite operations.
|
||||
@@ -98,6 +106,12 @@ public class MailAccount
|
||||
[Ignore]
|
||||
public MailAccountPreferences Preferences { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last time folder structure was synchronized.
|
||||
/// Used for optimization - skip folder sync if synced recently.
|
||||
/// </summary>
|
||||
public DateTime? LastFolderStructureSyncDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the account can perform ProfileInformation sync type.
|
||||
/// </summary>
|
||||
@@ -107,4 +121,6 @@ public class MailAccount
|
||||
/// Gets whether the account can perform AliasInformation sync type.
|
||||
/// </summary>
|
||||
public bool IsAliasSyncSupported => ProviderType == MailProviderType.Gmail;
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
public class WinoAccount
|
||||
{
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
public string AccountStatus { get; set; } = string.Empty;
|
||||
|
||||
public bool HasPassword { get; set; }
|
||||
|
||||
public bool HasGoogleLogin { get; set; }
|
||||
|
||||
public bool HasFacebookLogin { get; set; }
|
||||
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
|
||||
public DateTime AccessTokenExpiresAtUtc { get; set; }
|
||||
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
|
||||
public DateTime RefreshTokenExpiresAtUtc { get; set; }
|
||||
|
||||
public DateTime LastAuthenticatedUtc { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ public enum AccountCreationDialogState
|
||||
Idle,
|
||||
SigningIn,
|
||||
PreparingFolders,
|
||||
CalendarMetadataFetch,
|
||||
Completed,
|
||||
ManuelSetupWaiting,
|
||||
TestingConnection,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum AccountSetupStepStatus
|
||||
{
|
||||
Pending,
|
||||
InProgress,
|
||||
Succeeded,
|
||||
Failed
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
[Flags]
|
||||
public enum AiActionType
|
||||
{
|
||||
None = 0,
|
||||
Translate = 1,
|
||||
Rewrite = 2,
|
||||
Summarize = 4,
|
||||
}
|
||||
@@ -5,6 +5,5 @@ public enum CalendarDisplayType
|
||||
Day,
|
||||
Week,
|
||||
WorkWeek,
|
||||
Month,
|
||||
Year
|
||||
Month
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger to load more data.
|
||||
/// </summary>
|
||||
public enum CalendarInitInitiative
|
||||
{
|
||||
User,
|
||||
App
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Defines how a calendar item should be displayed in terms of availability.
|
||||
/// </summary>
|
||||
public enum CalendarItemShowAs
|
||||
{
|
||||
Free,
|
||||
Tentative,
|
||||
Busy,
|
||||
OutOfOffice,
|
||||
WorkingElsewhere
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
public enum CalendarItemStatus
|
||||
{
|
||||
NotResponded,
|
||||
Confirmed,
|
||||
Accepted,
|
||||
Tentative,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the source of a calendar item update.
|
||||
/// </summary>
|
||||
public enum CalendarItemUpdateSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Update originated from client-side UI changes (ApplyUIChanges).
|
||||
/// </summary>
|
||||
ClientUpdated,
|
||||
|
||||
/// <summary>
|
||||
/// Update originated from client-side UI revert (RevertUIChanges).
|
||||
/// </summary>
|
||||
ClientReverted,
|
||||
|
||||
/// <summary>
|
||||
/// Update originated from server synchronization or database operations.
|
||||
/// </summary>
|
||||
Server
|
||||
}
|
||||
@@ -5,6 +5,7 @@ public enum CalendarSynchronizationType
|
||||
ExecuteRequests, // Execute all requests in the queue.
|
||||
CalendarMetadata, // Sync calendar metadata.
|
||||
CalendarEvents, // Sync all events for all calendars.
|
||||
Strict, // Run metadata and event synchronization in sequence.
|
||||
SingleCalendar, // Sync events for only specified calendars.
|
||||
UpdateProfile // Update profile information only.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Grouping options for emails
|
||||
/// </summary>
|
||||
public enum EmailGroupingType
|
||||
{
|
||||
ByFromName,
|
||||
ByDate
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum ImapCalendarSupportMode
|
||||
{
|
||||
Disabled = 0,
|
||||
CalDav = 1,
|
||||
LocalOnly = 2
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum KeyboardShortcutAction
|
||||
{
|
||||
None,
|
||||
NewMail,
|
||||
ToggleReadUnread,
|
||||
ToggleFlag,
|
||||
ToggleArchive,
|
||||
Delete,
|
||||
Move,
|
||||
Reply,
|
||||
ReplyAll,
|
||||
Send,
|
||||
NewEvent
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
[Flags]
|
||||
public enum MailCopyChangeFlags
|
||||
{
|
||||
None = 0,
|
||||
Id = 1 << 0,
|
||||
FolderId = 1 << 1,
|
||||
ThreadId = 1 << 2,
|
||||
MessageId = 1 << 3,
|
||||
References = 1 << 4,
|
||||
InReplyTo = 1 << 5,
|
||||
FromName = 1 << 6,
|
||||
FromAddress = 1 << 7,
|
||||
Subject = 1 << 8,
|
||||
PreviewText = 1 << 9,
|
||||
CreationDate = 1 << 10,
|
||||
Importance = 1 << 11,
|
||||
IsRead = 1 << 12,
|
||||
IsFlagged = 1 << 13,
|
||||
IsFocused = 1 << 14,
|
||||
HasAttachments = 1 << 15,
|
||||
ItemType = 1 << 16,
|
||||
DraftId = 1 << 17,
|
||||
IsDraft = 1 << 18,
|
||||
FileId = 1 << 19,
|
||||
AssignedFolder = 1 << 20,
|
||||
AssignedAccount = 1 << 21,
|
||||
SenderContact = 1 << 22,
|
||||
UniqueId = 1 << 23,
|
||||
All = Id |
|
||||
FolderId |
|
||||
ThreadId |
|
||||
MessageId |
|
||||
References |
|
||||
InReplyTo |
|
||||
FromName |
|
||||
FromAddress |
|
||||
Subject |
|
||||
PreviewText |
|
||||
CreationDate |
|
||||
Importance |
|
||||
IsRead |
|
||||
IsFlagged |
|
||||
IsFocused |
|
||||
HasAttachments |
|
||||
ItemType |
|
||||
DraftId |
|
||||
IsDraft |
|
||||
FileId |
|
||||
AssignedFolder |
|
||||
AssignedAccount |
|
||||
SenderContact |
|
||||
UniqueId
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of mail item.
|
||||
/// </summary>
|
||||
public enum MailItemType
|
||||
{
|
||||
/// <summary>
|
||||
/// Regular mail message.
|
||||
/// </summary>
|
||||
Mail = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Calendar invitation (meeting request).
|
||||
/// </summary>
|
||||
CalendarInvitation = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Calendar response (meeting accepted, tentatively accepted, or declined).
|
||||
/// </summary>
|
||||
CalendarResponse = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Calendar cancellation (meeting cancelled).
|
||||
/// </summary>
|
||||
CalendarCancellation = 3
|
||||
}
|
||||
@@ -19,6 +19,18 @@ public enum FolderSynchronizerOperation
|
||||
RenameFolder,
|
||||
EmptyFolder,
|
||||
MarkFolderRead,
|
||||
DeleteFolder,
|
||||
CreateSubFolder,
|
||||
}
|
||||
|
||||
public enum CalendarSynchronizerOperation
|
||||
{
|
||||
CreateEvent,
|
||||
UpdateEvent,
|
||||
DeleteEvent,
|
||||
AcceptEvent,
|
||||
DeclineEvent,
|
||||
TentativeEvent,
|
||||
}
|
||||
|
||||
// UI requests
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the source of a mail update.
|
||||
/// </summary>
|
||||
public enum MailUpdateSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Update originated from client-side UI changes (ApplyUIChanges).
|
||||
/// </summary>
|
||||
ClientUpdated,
|
||||
|
||||
/// <summary>
|
||||
/// Update originated from client-side UI revert (RevertUIChanges).
|
||||
/// </summary>
|
||||
ClientReverted,
|
||||
|
||||
/// <summary>
|
||||
/// Update originated from server synchronization or database operations.
|
||||
/// </summary>
|
||||
Server
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Defines keyboard modifier keys that can be used in keyboard shortcuts.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ModifierKeys
|
||||
{
|
||||
None = 0,
|
||||
Control = 1,
|
||||
Alt = 2,
|
||||
Shift = 4,
|
||||
Windows = 8
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
public enum NavigationReferenceFrame
|
||||
{
|
||||
ShellFrame,
|
||||
InnerShellFrame,
|
||||
RenderingFrame
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the animation effect to use during a slide navigation transition.
|
||||
/// </summary>
|
||||
public enum NavigationTransitionEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// The navigation transition effect starts from the left edge of the frame.
|
||||
/// </summary>
|
||||
FromLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The navigation transition effect starts from the right edge of the frame.
|
||||
/// </summary>
|
||||
FromRight,
|
||||
|
||||
/// <summary>
|
||||
/// The navigation transition effect starts from the top edge of the frame.
|
||||
/// </summary>
|
||||
FromTop,
|
||||
|
||||
/// <summary>
|
||||
/// The navigation transition effect starts from the bottom edge of the frame.
|
||||
/// </summary>
|
||||
FromBottom
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum NewEventButtonBehavior
|
||||
{
|
||||
AskEachTime = 0,
|
||||
AlwaysUseSpecificCalendar = 1
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Print collation options.
|
||||
/// </summary>
|
||||
public enum PrintCollation
|
||||
{
|
||||
/// <summary>
|
||||
/// Default collation.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Collated printing.
|
||||
/// </summary>
|
||||
Collated = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Uncollated printing.
|
||||
/// </summary>
|
||||
Uncollated = 2
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Print color mode options.
|
||||
/// </summary>
|
||||
public enum PrintColorMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Default color mode.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Color printing.
|
||||
/// </summary>
|
||||
Color = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Grayscale printing.
|
||||
/// </summary>
|
||||
Grayscale = 2
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Print duplex (double-sided) options.
|
||||
/// </summary>
|
||||
public enum PrintDuplex
|
||||
{
|
||||
/// <summary>
|
||||
/// Default duplex mode.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Single-sided printing.
|
||||
/// </summary>
|
||||
Simplex = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Double-sided printing with pages flipped horizontally.
|
||||
/// </summary>
|
||||
DuplexShortEdge = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Double-sided printing with pages flipped vertically.
|
||||
/// </summary>
|
||||
DuplexLongEdge = 3
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Print media size options.
|
||||
/// </summary>
|
||||
public enum PrintMediaSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Default media size.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Letter size (8.5 x 11 inches).
|
||||
/// </summary>
|
||||
NorthAmericaLetter = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Legal size (8.5 x 14 inches).
|
||||
/// </summary>
|
||||
NorthAmericaLegal = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A4 size (210 x 297 mm).
|
||||
/// </summary>
|
||||
IsoA4 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A3 size (297 x 420 mm).
|
||||
/// </summary>
|
||||
IsoA3 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A5 size (148 x 210 mm).
|
||||
/// </summary>
|
||||
IsoA5 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Tabloid size (11 x 17 inches).
|
||||
/// </summary>
|
||||
NorthAmericaTabloid = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Executive size (7.25 x 10.5 inches).
|
||||
/// </summary>
|
||||
NorthAmericaExecutive = 7,
|
||||
|
||||
/// <summary>
|
||||
/// B4 size (250 x 353 mm).
|
||||
/// </summary>
|
||||
JisB4 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// B5 size (176 x 250 mm).
|
||||
/// </summary>
|
||||
JisB5 = 9
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Print orientation options.
|
||||
/// </summary>
|
||||
public enum PrintOrientation
|
||||
{
|
||||
/// <summary>
|
||||
/// Portrait orientation (default).
|
||||
/// </summary>
|
||||
Portrait = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Landscape orientation.
|
||||
/// </summary>
|
||||
Landscape = 1
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// What should happen to server app when the client is terminated.
|
||||
/// </summary>
|
||||
public enum ServerBackgroundMode
|
||||
{
|
||||
MinimizedTray, // Still runs, tray icon is visible.
|
||||
Invisible, // Still runs, tray icon is invisible.
|
||||
Terminate // Server is terminated as Wino terminates.
|
||||
}
|
||||
@@ -4,5 +4,6 @@ public enum SynchronizationCompletedState
|
||||
{
|
||||
Success, // All succeeded.
|
||||
Canceled, // Canceled by user or HTTP call.
|
||||
Failed // Exception.
|
||||
Failed, // Exception.
|
||||
PartiallyCompleted // Some folders succeeded, some failed.
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for the source of synchronization.
|
||||
/// Right now it can either be from the client or the server.
|
||||
/// </summary>
|
||||
public enum SynchronizationSource
|
||||
{
|
||||
Client,
|
||||
Server
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Categorizes synchronization errors by their root cause for targeted handling.
|
||||
/// </summary>
|
||||
public enum SynchronizerErrorCategory
|
||||
{
|
||||
/// <summary>
|
||||
/// Network-related issues: connection timeouts, DNS failures, socket errors.
|
||||
/// </summary>
|
||||
Network,
|
||||
|
||||
/// <summary>
|
||||
/// Authentication failures: invalid credentials, expired tokens, revoked access.
|
||||
/// </summary>
|
||||
Authentication,
|
||||
|
||||
/// <summary>
|
||||
/// Rate limiting: too many requests (HTTP 429), quota exceeded.
|
||||
/// </summary>
|
||||
RateLimit,
|
||||
|
||||
/// <summary>
|
||||
/// Resource not found: folder or message deleted externally (HTTP 404).
|
||||
/// </summary>
|
||||
ResourceNotFound,
|
||||
|
||||
/// <summary>
|
||||
/// Server errors: internal server errors (HTTP 5xx), service unavailable.
|
||||
/// </summary>
|
||||
ServerError,
|
||||
|
||||
/// <summary>
|
||||
/// Protocol errors: IMAP/SMTP command failures, malformed responses.
|
||||
/// </summary>
|
||||
ProtocolError,
|
||||
|
||||
/// <summary>
|
||||
/// Validation errors: invalid data, constraint violations.
|
||||
/// </summary>
|
||||
Validation,
|
||||
|
||||
/// <summary>
|
||||
/// Unknown or unclassified error.
|
||||
/// </summary>
|
||||
Unknown
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Classifies the severity of synchronization errors to determine retry behavior.
|
||||
/// </summary>
|
||||
public enum SynchronizerErrorSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// Transient error that should be retried with exponential backoff.
|
||||
/// Examples: network timeout, temporary server unavailability, rate limiting.
|
||||
/// </summary>
|
||||
Transient,
|
||||
|
||||
/// <summary>
|
||||
/// Error that can be recovered from by skipping the affected item/folder and continuing sync.
|
||||
/// Examples: folder deleted externally, message not found, permission denied on single item.
|
||||
/// </summary>
|
||||
Recoverable,
|
||||
|
||||
/// <summary>
|
||||
/// Fatal error that requires stopping synchronization and user intervention.
|
||||
/// Examples: account disabled, server permanently unavailable, critical configuration error.
|
||||
/// </summary>
|
||||
Fatal,
|
||||
|
||||
/// <summary>
|
||||
/// Authentication error that requires the user to re-authenticate.
|
||||
/// Examples: token expired, password changed, OAuth refresh failed.
|
||||
/// </summary>
|
||||
AuthRequired
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum WindowBackdropType
|
||||
{
|
||||
None,
|
||||
Mica,
|
||||
MicaAlt,
|
||||
DesktopAcrylic,
|
||||
AcrylicBase,
|
||||
AcrylicThin
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum WinoAddOnProductType
|
||||
{
|
||||
AI_PACK,
|
||||
UNLIMITED_ACCOUNTS
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum WinoApplicationMode
|
||||
{
|
||||
Mail,
|
||||
Calendar,
|
||||
Contacts,
|
||||
Settings
|
||||
}
|
||||
@@ -9,8 +9,8 @@ public enum WinoPage
|
||||
IdlePage,
|
||||
ComposePage,
|
||||
SettingsPage,
|
||||
ContactsPage,
|
||||
MailRenderingPage,
|
||||
WelcomePage,
|
||||
AccountDetailsPage,
|
||||
MergedAccountDetailsPage,
|
||||
ManageAccountsPage,
|
||||
@@ -21,13 +21,27 @@ public enum WinoPage
|
||||
MessageListPage,
|
||||
MailListPage,
|
||||
ReadComposePanePage,
|
||||
LanguageTimePage,
|
||||
AppPreferencesPage,
|
||||
SettingOptionsPage,
|
||||
AliasManagementPage,
|
||||
EditAccountDetailsPage,
|
||||
// Calendar
|
||||
ImapCalDavSettingsPage,
|
||||
KeyboardShortcutsPage,
|
||||
CalendarPage,
|
||||
CalendarSettingsPage,
|
||||
EventDetailsPage
|
||||
CalendarRenderingSettingsPage,
|
||||
CalendarNotificationSettingsPage,
|
||||
CalendarPreferenceSettingsPage,
|
||||
CalendarAccountSettingsPage,
|
||||
EventDetailsPage,
|
||||
CalendarEventComposePage,
|
||||
SignatureAndEncryptionPage,
|
||||
EmailTemplatesPage,
|
||||
CreateEmailTemplatePage,
|
||||
StoragePage,
|
||||
WinoAccountManagementPage,
|
||||
WelcomePageV2,
|
||||
WelcomeHostPage,
|
||||
ProviderSelectionPage,
|
||||
AccountSetupProgressPage,
|
||||
SpecialImapCredentialsPage
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum WinoServerConnectionStatus
|
||||
{
|
||||
None,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected,
|
||||
Failed
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// An exception thrown when the background task registration is failed.
|
||||
/// </summary>
|
||||
public class BackgroundTaskRegistrationFailedException : Exception { }
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Exceptions;
|
||||
|
||||
public sealed class CalendarEventComposeValidationException : Exception
|
||||
{
|
||||
public CalendarEventComposeValidationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -9,22 +9,18 @@ public class ImapClientPoolException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
public ImapClientPoolException(string message, CustomServerInformation customServerInformation, string protocolLog) : base(message)
|
||||
public ImapClientPoolException(string message, CustomServerInformation customServerInformation) : base(message)
|
||||
{
|
||||
CustomServerInformation = customServerInformation;
|
||||
ProtocolLog = protocolLog;
|
||||
}
|
||||
|
||||
public ImapClientPoolException(string message, string protocolLog) : base(message)
|
||||
public ImapClientPoolException(string message) : base(message)
|
||||
{
|
||||
ProtocolLog = protocolLog;
|
||||
}
|
||||
|
||||
public ImapClientPoolException(Exception innerException, string protocolLog) : base(innerException.Message, innerException)
|
||||
public ImapClientPoolException(Exception innerException) : base(innerException.Message, innerException)
|
||||
{
|
||||
ProtocolLog = protocolLog;
|
||||
}
|
||||
|
||||
public CustomServerInformation CustomServerInformation { get; }
|
||||
public string ProtocolLog { get; }
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using Wino.Core.Domain.Models.AutoDiscovery;
|
||||
|
||||
namespace Wino.Core.Domain.Exceptions;
|
||||
|
||||
public class ImapConnectionFailedPackage
|
||||
{
|
||||
public ImapConnectionFailedPackage(string errorMessage, string protocolLog, AutoDiscoverySettings settings)
|
||||
{
|
||||
ErrorMessage = errorMessage;
|
||||
ProtocolLog = protocolLog;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public AutoDiscoverySettings Settings { get; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public string ProtocolLog { get; }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// All server crash types. Wino Server ideally should not throw anything else than this Exception type.
|
||||
/// </summary>
|
||||
public class WinoServerException : Exception
|
||||
{
|
||||
public WinoServerException(string message) : base(message) { }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Extensions;
|
||||
|
||||
public static class CalendarRemoteEventIdExtensions
|
||||
{
|
||||
private const string ClientTrackingSeparator = "::";
|
||||
private const string CalDavClientTrackingPrefix = "caldav-";
|
||||
private const string LocalClientTrackingPrefix = "local-";
|
||||
|
||||
public static string GetProviderRemoteEventId(this string remoteEventId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(remoteEventId))
|
||||
return string.Empty;
|
||||
|
||||
var separatorIndex = remoteEventId.IndexOf(ClientTrackingSeparator, StringComparison.Ordinal);
|
||||
return separatorIndex >= 0 ? remoteEventId[..separatorIndex] : remoteEventId;
|
||||
}
|
||||
|
||||
public static Guid? GetClientTrackingId(this string remoteEventId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(remoteEventId))
|
||||
return null;
|
||||
|
||||
if (remoteEventId.Contains(ClientTrackingSeparator, StringComparison.Ordinal))
|
||||
{
|
||||
var trackedPart = remoteEventId[(remoteEventId.LastIndexOf(ClientTrackingSeparator, StringComparison.Ordinal) + ClientTrackingSeparator.Length)..];
|
||||
if (TryParseGuid(trackedPart, out var trackedId))
|
||||
return trackedId;
|
||||
}
|
||||
|
||||
if (TryParseGuid(remoteEventId, out var directId))
|
||||
return directId;
|
||||
|
||||
if (remoteEventId.StartsWith(CalDavClientTrackingPrefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
TryParseGuid(remoteEventId[CalDavClientTrackingPrefix.Length..], out var calDavId))
|
||||
{
|
||||
return calDavId;
|
||||
}
|
||||
|
||||
if (remoteEventId.StartsWith(LocalClientTrackingPrefix, StringComparison.OrdinalIgnoreCase) &&
|
||||
TryParseGuid(remoteEventId[LocalClientTrackingPrefix.Length..], out var localId))
|
||||
{
|
||||
return localId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string WithClientTrackingId(this string providerRemoteEventId, Guid? clientTrackingId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(providerRemoteEventId) || !clientTrackingId.HasValue)
|
||||
return providerRemoteEventId ?? string.Empty;
|
||||
|
||||
return $"{providerRemoteEventId}{ClientTrackingSeparator}{clientTrackingId.Value:N}";
|
||||
}
|
||||
|
||||
private static bool TryParseGuid(string value, out Guid parsedGuid)
|
||||
{
|
||||
parsedGuid = Guid.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return false;
|
||||
|
||||
return Guid.TryParseExact(value, "N", out parsedGuid) || Guid.TryParse(value, out parsedGuid);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Extensions;
|
||||
@@ -29,4 +30,60 @@ public static class DateTimeExtensions
|
||||
// Start loading from this date instead of visible date.
|
||||
return date.AddDays(-diff).Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a datetime from source timezone into local timezone.
|
||||
/// If timezone lookup fails, returns original value.
|
||||
/// </summary>
|
||||
public static DateTime ToLocalTimeFromTimeZone(this DateTime dateTime, string sourceTimeZoneId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sourceTimeZoneId))
|
||||
return dateTime;
|
||||
|
||||
try
|
||||
{
|
||||
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(sourceTimeZoneId);
|
||||
var localTimeZone = TimeZoneInfo.Local;
|
||||
var unspecifiedDateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
|
||||
|
||||
return TimeZoneInfo.ConvertTime(unspecifiedDateTime, sourceTimeZone, localTimeZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return dateTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts local datetime into target timezone.
|
||||
/// If timezone lookup fails, returns original value.
|
||||
/// </summary>
|
||||
public static DateTime ToTimeZoneFromLocal(this DateTime localDateTime, string targetTimeZoneId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(targetTimeZoneId))
|
||||
return localDateTime;
|
||||
|
||||
try
|
||||
{
|
||||
var sourceTimeZone = TimeZoneInfo.Local;
|
||||
var targetTimeZone = TimeZoneInfo.FindSystemTimeZoneById(targetTimeZoneId);
|
||||
var unspecifiedDateTime = DateTime.SpecifyKind(localDateTime, DateTimeKind.Unspecified);
|
||||
|
||||
return TimeZoneInfo.ConvertTime(unspecifiedDateTime, sourceTimeZone, targetTimeZone);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return localDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime GetLocalStartDate(this CalendarItem calendarItem)
|
||||
=> calendarItem.IsAllDayEvent
|
||||
? calendarItem.StartDate
|
||||
: calendarItem.StartDate.ToLocalTimeFromTimeZone(calendarItem.StartTimeZone);
|
||||
|
||||
public static DateTime GetLocalEndDate(this CalendarItem calendarItem)
|
||||
=> calendarItem.IsAllDayEvent
|
||||
? calendarItem.EndDate
|
||||
: calendarItem.EndDate.ToLocalTimeFromTimeZone(calendarItem.EndTimeZone);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Wino.Core.Domain.Extensions;
|
||||
|
||||
public static class MailHeaderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Strips angle brackets from a Message-ID or In-Reply-To value.
|
||||
/// RFC 5322 Message-IDs are formatted as <id@domain>, but MimeKit
|
||||
/// properties store them without brackets. This normalizes raw header
|
||||
/// values to match MimeKit's convention.
|
||||
/// </summary>
|
||||
public static string StripAngleBrackets(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return value;
|
||||
|
||||
value = value.Trim();
|
||||
|
||||
if (value.StartsWith("<") && value.EndsWith(">"))
|
||||
return value.Substring(1, value.Length - 2);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a raw RFC References header value into semicolon-separated Message-IDs
|
||||
/// without angle brackets. Raw References headers contain space-separated bracketed IDs
|
||||
/// like "<id1@domain> <id2@domain>". This converts them to "id1@domain;id2@domain".
|
||||
/// </summary>
|
||||
public static string NormalizeReferences(string rawReferences)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rawReferences)) return rawReferences;
|
||||
|
||||
var ids = rawReferences
|
||||
.Split(new[] { ' ', '\t', '\r', '\n', ';', ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(StripAngleBrackets)
|
||||
.Where(id => !string.IsNullOrEmpty(id));
|
||||
|
||||
return string.Join(";", ids);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -8,8 +10,11 @@ public interface IAccountCalendar
|
||||
string TextColorHex { get; set; }
|
||||
string BackgroundColorHex { get; set; }
|
||||
bool IsPrimary { get; set; }
|
||||
bool IsSynchronizationEnabled { get; set; }
|
||||
Guid AccountId { get; set; }
|
||||
string RemoteCalendarId { get; set; }
|
||||
bool IsExtended { get; set; }
|
||||
CalendarItemShowAs DefaultShowAs { get; set; }
|
||||
Guid Id { get; set; }
|
||||
MailAccount MailAccount { get; set; }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,27 @@ namespace Wino.Core.Domain.Interfaces;
|
||||
public interface IAccountMenuItem : IMenuItem
|
||||
{
|
||||
bool IsEnabled { get; set; }
|
||||
double SynchronizationProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculated synchronization progress percentage (0-100). -1 for indeterminate.
|
||||
/// </summary>
|
||||
double SynchronizationProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total items to sync. 0 for indeterminate progress.
|
||||
/// </summary>
|
||||
int TotalItemsToSync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remaining items to sync.
|
||||
/// </summary>
|
||||
int RemainingItemsToSync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current synchronization status message.
|
||||
/// </summary>
|
||||
string SynchronizationStatus { get; set; }
|
||||
|
||||
int UnreadItemCount { get; set; }
|
||||
IEnumerable<MailAccount> HoldingAccounts { get; }
|
||||
void UpdateAccount(MailAccount account);
|
||||
|
||||
@@ -171,4 +171,19 @@ public interface IAccountService
|
||||
/// <returns>Whether the notifications should be created after sync or not.</returns>
|
||||
Task<bool> IsNotificationsEnabled(Guid accountId);
|
||||
Task UpdateAccountCustomServerInformationAsync(CustomServerInformation customServerInformation);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the last folder structure sync date for the given account.
|
||||
/// Used for optimization to skip folder sync if it was done recently.
|
||||
/// </summary>
|
||||
/// <param name="accountId">Account id.</param>
|
||||
Task UpdateLastFolderStructureSyncDateAsync(Guid accountId);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if folder structure should be synced based on the configured interval.
|
||||
/// Returns true if LastFolderStructureSyncDate is null or older than the interval.
|
||||
/// </summary>
|
||||
/// <param name="accountId">Account id.</param>
|
||||
/// <param name="syncInterval">Minimum interval between folder syncs.</param>
|
||||
Task<bool> ShouldSyncFolderStructureAsync(Guid accountId, TimeSpan syncInterval);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Wino.Core.Domain.Models.Ai;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IAiActionOptionsService
|
||||
{
|
||||
IReadOnlyList<AiTranslateLanguageOption> GetTranslateLanguageOptions();
|
||||
IReadOnlyList<AiRewriteModeOption> GetRewriteModeOptions();
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Models.AutoDiscovery;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Searches for Auto Discovery settings for custom mail accounts.
|
||||
/// Searches for auto-discovery settings for custom mail accounts.
|
||||
/// </summary>
|
||||
public interface IAutoDiscoveryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to return the best mail server settings using different techniques.
|
||||
/// </summary>
|
||||
/// <param name="mailAddress">Address to search settings for.</param>
|
||||
/// <returns>CustomServerInformation with only settings applied.</returns>
|
||||
Task<AutoDiscoverySettings> GetAutoDiscoverySettings(AutoDiscoveryMinimalSettings autoDiscoveryMinimalSettings);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to resolve a CalDAV endpoint for the mailbox address.
|
||||
/// </summary>
|
||||
Task<Uri> DiscoverCalDavServiceUriAsync(string mailAddress, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IBackgroundTaskService
|
||||
{
|
||||
/// <summary>
|
||||
/// Unregisters all background tasks once.
|
||||
/// This is used to clean up the background tasks when the app is updated.
|
||||
/// </summary>
|
||||
void UnregisterAllBackgroundTask();
|
||||
|
||||
/// <summary>
|
||||
/// Registers required background tasks.
|
||||
/// </summary>
|
||||
Task RegisterBackgroundTasksAsync();
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
@@ -23,6 +25,23 @@ public interface IBaseSynchronizer
|
||||
/// <param name="request">Request to queue.</param>
|
||||
void QueueRequest(IRequestBase request);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether there is an in-progress (queued or currently executing) operation for the given mail unique id.
|
||||
/// </summary>
|
||||
/// <param name="mailUniqueId">Mail unique id to check.</param>
|
||||
bool HasPendingOperation(Guid mailUniqueId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns mail unique ids that currently have queued or executing operations.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<Guid> GetPendingOperationUniqueIds();
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether there is an in-progress (queued or currently executing) operation for the given calendar item id.
|
||||
/// </summary>
|
||||
/// <param name="calendarItemId">Calendar item id to check.</param>
|
||||
bool HasPendingCalendarOperation(Guid calendarItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes profile information with the server.
|
||||
/// Sender name and Profile picture are updated.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface ICalDavClient
|
||||
{
|
||||
Task<IReadOnlyList<CalDavCalendar>> DiscoverCalendarsAsync(
|
||||
CalDavConnectionSettings connectionSettings,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyList<CalDavCalendarEvent>> GetCalendarEventsAsync(
|
||||
CalDavConnectionSettings connectionSettings,
|
||||
CalDavCalendar calendar,
|
||||
DateTimeOffset startUtc,
|
||||
DateTimeOffset endUtc,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task UpsertCalendarEventAsync(
|
||||
CalDavConnectionSettings connectionSettings,
|
||||
CalDavCalendar calendar,
|
||||
string remoteEventId,
|
||||
string icsContent,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task DeleteCalendarEventAsync(
|
||||
CalDavConnectionSettings connectionSettings,
|
||||
CalDavCalendar calendar,
|
||||
string remoteEventId,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface ICalendarDialogService : IDialogServiceBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Persists CalDAV ICS payloads on disk for IMAP accounts.
|
||||
/// </summary>
|
||||
public interface ICalendarIcsFileService
|
||||
{
|
||||
Task SaveCalendarItemIcsAsync(Guid accountId, Guid calendarId, Guid calendarItemId, string remoteEventId, string remoteResourceHref, string eTag, string icsContent);
|
||||
Task<string> GetCalendarItemIcsETagAsync(Guid accountId, Guid calendarId, Guid calendarItemId);
|
||||
Task DeleteCalendarItemIcsAsync(Guid accountId, Guid calendarItemId);
|
||||
Task DeleteCalendarIcsForCalendarAsync(Guid accountId, Guid calendarId);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Itenso.TimePeriod;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -19,4 +20,13 @@ public interface ICalendarItem
|
||||
bool IsRecurringChild { get; }
|
||||
bool IsRecurringParent { get; }
|
||||
bool IsRecurringEvent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display title for this calendar item when rendered in a specific day.
|
||||
/// For multi-day events, includes start/end time indicators.
|
||||
/// </summary>
|
||||
/// <param name="displayingPeriod">The period of the day where this item is being rendered.</param>
|
||||
/// <param name="calendarSettings">Calendar settings for time formatting.</param>
|
||||
/// <returns>The formatted title string.</returns>
|
||||
string GetDisplayTitle(ITimePeriod displayingPeriod, CalendarSettings calendarSettings);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
using Itenso.TimePeriod;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Temporarily to enforce CalendarItemViewModel. Used in CalendarEventCollection.
|
||||
@@ -6,4 +10,23 @@
|
||||
public interface ICalendarItemViewModel
|
||||
{
|
||||
bool IsSelected { get; set; }
|
||||
|
||||
bool IsBusy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The period of the day where this item is currently being displayed.
|
||||
/// </summary>
|
||||
ITimePeriod DisplayingPeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calendar settings for time formatting.
|
||||
/// </summary>
|
||||
CalendarSettings CalendarSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the view model's underlying CalendarItem from new data.
|
||||
/// This allows in-place updates without removing and re-adding items.
|
||||
/// </summary>
|
||||
/// <param name="calendarItem">The updated calendar item data.</param>
|
||||
void UpdateFrom(CalendarItem calendarItem);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Itenso.TimePeriod;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
@@ -11,12 +13,22 @@ public interface ICalendarService
|
||||
Task<List<AccountCalendar>> GetAccountCalendarsAsync(Guid accountId);
|
||||
Task<AccountCalendar> GetAccountCalendarAsync(Guid accountCalendarId);
|
||||
Task DeleteCalendarItemAsync(Guid calendarItemId);
|
||||
Task DeleteCalendarItemAsync(string calendarRemoteEventId, Guid calendarId);
|
||||
|
||||
Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||
Task InsertAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||
Task SetPrimaryCalendarAsync(Guid accountId, Guid accountCalendarId);
|
||||
Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
|
||||
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, DayRangeRenderModel dayRangeRenderModel);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves calendar events for a given calendar within the specified time period.
|
||||
/// </summary>
|
||||
/// <param name="calendar">The calendar to retrieve events from.</param>
|
||||
/// <param name="period">The time period to query events for.</param>
|
||||
/// <returns>List of calendar items that fall within the requested period.</returns>
|
||||
Task<List<CalendarItem>> GetCalendarEventsAsync(IAccountCalendar calendar, ITimePeriod period);
|
||||
|
||||
Task<CalendarItem> GetCalendarItemAsync(Guid accountCalendarId, string remoteEventId);
|
||||
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);
|
||||
|
||||
@@ -28,4 +40,43 @@ public interface ICalendarService
|
||||
Task<CalendarItem> GetCalendarItemAsync(Guid id);
|
||||
Task<List<CalendarEventAttendee>> GetAttendeesAsync(Guid calendarEventTrackingId);
|
||||
Task<List<CalendarEventAttendee>> ManageEventAttendeesAsync(Guid calendarItemId, List<CalendarEventAttendee> allAttendees);
|
||||
Task UpdateCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
|
||||
Task<List<CalendarItem>> SearchCalendarItemsAsync(string searchQuery, int limit, CancellationToken cancellationToken = default);
|
||||
Task<List<Reminder>> GetRemindersAsync(Guid calendarItemId);
|
||||
Task SaveRemindersAsync(Guid calendarItemId, List<Reminder> reminders);
|
||||
Task SnoozeCalendarItemAsync(Guid calendarItemId, DateTime snoozedUntilLocal);
|
||||
|
||||
/// <summary>
|
||||
/// Checks due reminder windows and returns reminder notifications that should trigger now.
|
||||
/// </summary>
|
||||
Task<List<CalendarReminderNotificationRequest>> CheckAndNotifyAsync(DateTime lastCheckLocal, DateTime nowLocal, ISet<string> sentReminderKeys, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets predefined reminder options in minutes (1 Hour, 30 Min, 15 Min, 5 Min, 1 Min).
|
||||
/// </summary>
|
||||
int[] GetPredefinedReminderMinutes();
|
||||
|
||||
#region Attachments
|
||||
|
||||
/// <summary>
|
||||
/// Gets all attachments for a calendar event.
|
||||
/// </summary>
|
||||
Task<List<CalendarAttachment>> GetAttachmentsAsync(Guid calendarItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts or updates calendar attachments.
|
||||
/// </summary>
|
||||
Task InsertOrReplaceAttachmentsAsync(List<CalendarAttachment> attachments);
|
||||
|
||||
/// <summary>
|
||||
/// Marks an attachment as downloaded and updates its local file path.
|
||||
/// </summary>
|
||||
Task MarkAttachmentDownloadedAsync(Guid attachmentId, string localFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all attachments for a calendar item.
|
||||
/// </summary>
|
||||
Task DeleteAttachmentsAsync(Guid calendarItemId);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Manages contact picture files stored on disk instead of as base64 in SQLite,
|
||||
/// eliminating DB bloat and enabling native WIC hardware-accelerated image loading.
|
||||
/// </summary>
|
||||
public interface IContactPictureFileService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the full file path for the given file ID, or null if the file does not exist on disk.
|
||||
/// </summary>
|
||||
string GetContactPicturePath(Guid fileId);
|
||||
|
||||
/// <summary>
|
||||
/// Saves raw image bytes to disk and returns the new file ID.
|
||||
/// </summary>
|
||||
Task<Guid> SaveContactPictureAsync(byte[] imageData);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the picture file for the given file ID if it exists.
|
||||
/// </summary>
|
||||
Task DeleteContactPictureAsync(Guid fileId);
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MimeKit;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Models.Contacts;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -9,6 +12,30 @@ public interface IContactService
|
||||
{
|
||||
Task<List<AccountContact>> GetAddressInformationAsync(string queryText);
|
||||
Task<AccountContact> GetAddressInformationByAddressAsync(string address);
|
||||
Task<List<AccountContact>> GetContactsByAddressesAsync(IEnumerable<string> addresses);
|
||||
Task SaveAddressInformationAsync(MimeMessage message);
|
||||
Task SaveAddressInformationAsync(IEnumerable<AccountContact> contacts);
|
||||
Task<AccountContact> CreateNewContactAsync(string address, string displayName);
|
||||
|
||||
// Paged contact queries for ContactsPage
|
||||
Task<List<AccountContact>> GetAllContactsAsync();
|
||||
Task<List<AccountContact>> SearchContactsAsync(string searchQuery);
|
||||
Task<PagedContactsResult> GetContactsPageAsync(int offset, int pageSize, string searchQuery = null, bool excludeRootContacts = false);
|
||||
Task<AccountContact> UpdateContactAsync(AccountContact contact);
|
||||
Task DeleteContactAsync(string address);
|
||||
Task DeleteContactsAsync(IEnumerable<string> addresses);
|
||||
|
||||
// Group / distribution list support
|
||||
Task<List<ContactGroup>> GetGroupsAsync();
|
||||
Task<ContactGroup> CreateGroupAsync(string name, string description = null);
|
||||
Task DeleteGroupAsync(Guid groupId);
|
||||
Task<List<AccountContact>> GetGroupMembersAsync(Guid groupId);
|
||||
Task AddGroupMemberAsync(Guid groupId, string memberAddress);
|
||||
Task RemoveGroupMemberAsync(Guid groupId, string memberAddress);
|
||||
|
||||
/// <summary>
|
||||
/// Expands a contact group to the individual <see cref="AccountContact"/> entries of its members.
|
||||
/// Returns an empty list if the group does not exist or has no members.
|
||||
/// </summary>
|
||||
Task<List<AccountContact>> ExpandGroupAsync(Guid groupId);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
@@ -8,6 +8,6 @@ namespace Wino.Core.Domain.Interfaces;
|
||||
public interface IContextMenuItemService
|
||||
{
|
||||
IEnumerable<FolderOperationMenuItem> GetFolderContextMenuActions(IBaseFolderMenuItem folderInformation);
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemContextMenuActions(IEnumerable<IMailItem> selectedMailItems);
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemRenderMenuActions(IMailItem mailItem, bool isDarkEditor);
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemContextMenuActions(IEnumerable<MailCopy> selectedMailItems);
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemRenderMenuActions(MailCopy mailItem, bool isDarkEditor);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Menus;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
@@ -18,12 +18,12 @@ public interface IContextMenuProvider
|
||||
/// </summary>
|
||||
/// <param name="folderInformation">Current folder that asks for the menu items.</param>
|
||||
/// <param name="selectedMailItems">Selected menu items in the given folder.</param>
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemContextMenuActions(IMailItemFolder folderInformation, IEnumerable<IMailItem> selectedMailItems);
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemContextMenuActions(IMailItemFolder folderInformation, IEnumerable<MailCopy> selectedMailItems);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates and returns available mail operations for mail rendering CommandBar.
|
||||
/// </summary>
|
||||
/// <param name="mailItem">Rendered mail item.</param>
|
||||
/// <param name="activeFolder">Folder that mail item belongs to.</param>
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemRenderMenuActions(IMailItem mailItem, IMailItemFolder activeFolder, bool isDarkEditor);
|
||||
IEnumerable<MailOperationMenuItem> GetMailItemRenderMenuActions(MailCopy mailItem, IMailItemFolder activeFolder, bool isDarkEditor);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Common;
|
||||
using Wino.Core.Domain.Models.Printing;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -27,5 +28,7 @@ public interface IDialogServiceBase
|
||||
Task<AccountCreationDialogResult> ShowAccountProviderSelectionDialogAsync(List<IProviderDetail> availableProviders);
|
||||
IAccountCreationDialog GetAccountCreationDialog(AccountCreationDialogResult accountCreationDialogResult);
|
||||
Task<List<SharedFile>> PickFilesAsync(params object[] typeFilters);
|
||||
Task<List<PickedFileMetadata>> PickFilesMetadataAsync(params object[] typeFilters);
|
||||
Task<string> PickFilePathAsync(string saveFileName);
|
||||
Task<WebView2PrintSettingsModel> ShowPrintDialogAsync(WebView2PrintSettingsModel initialSettings = null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IEmailTemplateService
|
||||
{
|
||||
Task<List<EmailTemplate>> GetEmailTemplatesAsync();
|
||||
Task<EmailTemplate> GetEmailTemplateAsync(Guid templateId);
|
||||
Task<EmailTemplate> CreateEmailTemplateAsync(EmailTemplate template);
|
||||
Task<EmailTemplate> UpdateEmailTemplateAsync(EmailTemplate template);
|
||||
Task<EmailTemplate> DeleteEmailTemplateAsync(EmailTemplate template);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
@@ -20,7 +21,7 @@ public interface IBaseFolderMenuItem : IMenuItem
|
||||
int UnreadItemCount { get; set; }
|
||||
SpecialFolderType SpecialFolderType { get; }
|
||||
IEnumerable<IMailItemFolder> HandlingFolders { get; }
|
||||
IEnumerable<IMenuItem> SubMenuItems { get; }
|
||||
ObservableCollection<IMenuItem> SubMenuItems { get; }
|
||||
bool IsMoveTarget { get; }
|
||||
bool IsSticky { get; }
|
||||
bool IsSystemFolder { get; }
|
||||
|
||||
@@ -79,6 +79,13 @@ public interface IFolderService
|
||||
/// <param name="folder">Folder to update.</param>
|
||||
Task UpdateFolderAsync(MailItemFolder folder);
|
||||
|
||||
/// <summary>
|
||||
/// Updates only IMAP HighestModeSeq for the given folder.
|
||||
/// </summary>
|
||||
/// <param name="folderId">Folder id to update.</param>
|
||||
/// <param name="highestModeSeq">Latest known mod-seq value.</param>
|
||||
Task UpdateFolderHighestModeSeqAsync(Guid folderId, long highestModeSeq);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the active folder menu items for the given account for UI.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IGmailThreadingStrategy : IThreadingStrategy { }
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IImapAccountCreationDialog : IAccountCreationDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the custom server information from the dialog..
|
||||
/// </summary>
|
||||
/// <returns>Null if canceled.</returns>
|
||||
Task<CustomServerInformation> GetCustomServerInformationAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Displays preparing folders page.
|
||||
/// </summary>
|
||||
void ShowPreparingFolders();
|
||||
|
||||
/// <summary>
|
||||
/// Updates account properties for the welcome imap setup dialog and starts the setup.
|
||||
/// </summary>
|
||||
/// <param name="account">Account properties.</param>
|
||||
void StartImapConnectionSetup(MailAccount account);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using MailKit.Net.Imap;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a synchronization strategy for synchronizing IMAP folders based on the server capabilities.
|
||||
/// </summary>
|
||||
public interface IImapSynchronizationStrategyProvider
|
||||
{
|
||||
IImapSynchronizerStrategy GetSynchronizationStrategy(IImapClient client);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IImapSynchronizerStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronizes given folder with the ImapClient client from the client pool.
|
||||
/// </summary>
|
||||
/// <param name="client">Client to perform sync with. I love Mira and Jasminka</param>
|
||||
/// <param name="folder">Folder to synchronize.</param>
|
||||
/// <param name="synchronizer">Imap synchronizer that downloads messages.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>List of new downloaded message ids that don't exist locally.</returns>
|
||||
Task<List<string>> HandleSynchronizationAsync(IImapClient client,
|
||||
MailItemFolder folder,
|
||||
IImapSynchronizer synchronizer,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads given set of messages from the folder.
|
||||
/// Folder is expected to be opened and synchronizer is connected.
|
||||
/// </summary>
|
||||
/// <param name="synchronizer">Synchronizer that performs the action.</param>
|
||||
/// <param name="remoteFolder">Remote folder to download messages from.</param>
|
||||
/// <param name="localFolder">Local folder to assign mails to.</param>
|
||||
/// <param name="uniqueIdSet">Set of message uniqueids.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DownloadMessagesAsync(IImapSynchronizer synchronizer,
|
||||
IMailFolder remoteFolder,
|
||||
MailItemFolder localFolder,
|
||||
UniqueIdSet uniqueIdSet,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IImapThreadingStrategy : IThreadingStrategy { }
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing keyboard shortcuts for mail operations.
|
||||
/// </summary>
|
||||
public interface IKeyboardShortcutService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all available keyboard shortcuts.
|
||||
/// </summary>
|
||||
/// <returns>Collection of keyboard shortcuts.</returns>
|
||||
Task<IEnumerable<KeyboardShortcut>> GetKeyboardShortcutsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets enabled keyboard shortcuts only.
|
||||
/// </summary>
|
||||
/// <returns>Collection of enabled keyboard shortcuts.</returns>
|
||||
Task<IEnumerable<KeyboardShortcut>> GetEnabledKeyboardShortcutsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates a keyboard shortcut.
|
||||
/// </summary>
|
||||
/// <param name="shortcut">The keyboard shortcut to save.</param>
|
||||
/// <returns>The saved keyboard shortcut.</returns>
|
||||
Task<KeyboardShortcut> SaveKeyboardShortcutAsync(KeyboardShortcut shortcut);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a keyboard shortcut.
|
||||
/// </summary>
|
||||
/// <param name="shortcutId">The ID of the shortcut to delete.</param>
|
||||
Task DeleteKeyboardShortcutAsync(Guid shortcutId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyboard shortcut for the given key combination in a specific mode.
|
||||
/// </summary>
|
||||
/// <param name="mode">The application mode to search within.</param>
|
||||
/// <param name="key">The pressed key.</param>
|
||||
/// <param name="modifierKeys">The modifier keys pressed.</param>
|
||||
/// <returns>The matching shortcut if found, otherwise null.</returns>
|
||||
Task<KeyboardShortcut> GetShortcutForKeyAsync(WinoApplicationMode mode, string key, ModifierKeys modifierKeys);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a key combination is already assigned to another shortcut.
|
||||
/// </summary>
|
||||
/// <param name="mode">The application mode to check within.</param>
|
||||
/// <param name="key">The key to check.</param>
|
||||
/// <param name="modifierKeys">The modifier keys to check.</param>
|
||||
/// <param name="excludeShortcutId">Optional ID to exclude from the check (for updates).</param>
|
||||
/// <returns>True if the combination is already used, false otherwise.</returns>
|
||||
Task<bool> IsKeyCombinationInUseAsync(WinoApplicationMode mode, string key, ModifierKeys modifierKeys, Guid? excludeShortcutId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates default keyboard shortcuts for common mail operations.
|
||||
/// </summary>
|
||||
Task CreateDefaultShortcutsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Resets all shortcuts to defaults.
|
||||
/// </summary>
|
||||
Task ResetToDefaultShortcutsAsync();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
using System;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -13,4 +14,5 @@ public interface ILaunchProtocolService
|
||||
/// Used to handle mailto links.
|
||||
/// </summary>
|
||||
MailToUri MailToUri { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
@@ -17,6 +22,7 @@ public interface IMailDialogService : IDialogServiceBase
|
||||
// Custom dialogs
|
||||
Task<IMailItemFolder> ShowMoveMailFolderDialogAsync(List<IMailItemFolder> availableFolders);
|
||||
Task<MailAccount> ShowAccountPickerDialogAsync(List<MailAccount> availableAccounts);
|
||||
Task<AccountCalendarPickingResult> ShowSingleCalendarPickerDialogAsync(List<CalendarPickerAccountGroup> availableCalendarGroups);
|
||||
|
||||
/// <summary>
|
||||
/// Displays a dialog to the user for reordering accounts.
|
||||
@@ -37,7 +43,7 @@ public interface IMailDialogService : IDialogServiceBase
|
||||
/// Presents a dialog to the user for signature creation/modification.
|
||||
/// </summary>
|
||||
/// <returns>Signature information. Null if canceled.</returns>
|
||||
Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature signatureModel = null);
|
||||
Task<AccountSignature> ShowSignatureEditorDialog(AccountSignature? signatureModel = null);
|
||||
|
||||
/// <summary>
|
||||
/// Presents a dialog to the user for account alias creation/modification.
|
||||
@@ -49,4 +55,26 @@ public interface IMailDialogService : IDialogServiceBase
|
||||
/// Presents a dialog to the user to show email source.
|
||||
/// </summary>
|
||||
Task ShowMessageSourceDialogAsync(string messageSource);
|
||||
|
||||
/// <summary>
|
||||
/// Presents a dialog to the user for keyboard shortcut creation/modification.
|
||||
/// </summary>
|
||||
/// <param name="existingShortcut">Existing shortcut to edit, or null for new shortcut.</param>
|
||||
/// <returns>Dialog result with shortcut information.</returns>
|
||||
#pragma warning disable CS8625
|
||||
Task<KeyboardShortcutDialogResult> ShowKeyboardShortcutDialogAsync(KeyboardShortcut existingShortcut = null);
|
||||
#pragma warning restore CS8625
|
||||
|
||||
/// <summary>
|
||||
/// Presents a dialog to the user for contact creation/modification.
|
||||
/// </summary>
|
||||
/// <param name="contact">Existing contact to edit, or null for new contact.</param>
|
||||
/// <returns>Contact information. Null if canceled.</returns>
|
||||
Task<AccountContact?> ShowEditContactDialogAsync(AccountContact? contact = null);
|
||||
|
||||
Task<WinoAccount?> ShowWinoAccountRegistrationDialogAsync();
|
||||
|
||||
Task<WinoAccount?> ShowWinoAccountLoginDialogAsync();
|
||||
|
||||
Task<WinoAccountSyncExportResult?> ShowWinoAccountExportDialogAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IMailHashContainer
|
||||
{
|
||||
IEnumerable<Guid> GetContainingIds();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Shared display contract for mail list item rendering.
|
||||
/// Implemented by both single mail and thread mail view models.
|
||||
/// </summary>
|
||||
public interface IMailItemDisplayInformation : INotifyPropertyChanged
|
||||
{
|
||||
string Subject { get; }
|
||||
string FromName { get; }
|
||||
string FromAddress { get; }
|
||||
string PreviewText { get; }
|
||||
bool IsRead { get; }
|
||||
bool IsDraft { get; }
|
||||
bool HasAttachments { get; }
|
||||
bool IsCalendarEvent { get; }
|
||||
bool IsFlagged { get; }
|
||||
DateTime CreationDate { get; }
|
||||
Guid? ContactPictureFileId { get; }
|
||||
bool ThumbnailUpdatedEvent { get; }
|
||||
bool IsThreadExpanded { get; }
|
||||
AccountContact SenderContact { get; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface IMailListItemSorting
|
||||
{
|
||||
DateTime SortingDate { get; }
|
||||
string SortingName { get; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user