Handle read-only calendars

This commit is contained in:
Burak Kaan Köse
2026-04-14 17:52:38 +02:00
parent feff929333
commit 0610096b78
14 changed files with 115 additions and 2 deletions
@@ -86,6 +86,7 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
get
{
if (SelectedQuickEventAccountCalendar == null ||
SelectedQuickEventAccountCalendar.IsReadOnly ||
SelectedQuickEventDate == null ||
string.IsNullOrWhiteSpace(EventName) ||
string.IsNullOrWhiteSpace(SelectedStartTimeString) ||
@@ -204,6 +205,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
if (DisplayDetailsCalendarItemViewModel?.CalendarItem == null)
return;
if (DisplayDetailsCalendarItemViewModel.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
if (DisplayDetailsCalendarItemViewModel.CalendarItem.IsRecurringParent)
{
var confirmed = await _dialogService.ShowConfirmationDialogAsync(
@@ -460,6 +467,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
[RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(CanSaveQuickEvent))]
private async Task SaveQuickEventAsync()
{
if (SelectedQuickEventAccountCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
var startDate = IsAllDay ? SelectedQuickEventDate.Value.Date : QuickEventStartTime;
var endDate = IsAllDay ? SelectedQuickEventDate.Value.Date.AddDays(1) : QuickEventEndTime;
var composeResult = new CalendarEventComposeResult
@@ -553,6 +566,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
return;
}
if (calendarItem.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
var normalizedTargetStart = calendarItem.IsAllDayEvent
? targetStart.Date
: targetStart;
@@ -1195,6 +1214,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
if (targetItem == null)
return;
if (targetItem.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
if (targetItem.IsRecurringParent)
{
var confirmed = await _dialogService.ShowConfirmationDialogAsync(
@@ -1221,6 +1246,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
if (targetItem == null || targetItem.ShowAs == showAs)
return;
if (targetItem.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
var originalItem = await _calendarService.GetCalendarItemAsync(targetItem.Id).ConfigureAwait(false);
var attendees = await _calendarService.GetAttendeesAsync(targetItem.Id).ConfigureAwait(false);
@@ -1245,6 +1276,12 @@ public partial class CalendarPageViewModel : CalendarBaseViewModel,
if (targetItem == null)
return;
if (targetItem.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
var operation = responseStatus switch
{
CalendarItemStatus.Accepted => CalendarSynchronizerOperation.AcceptEvent,
@@ -55,6 +55,12 @@ public partial class AccountCalendarViewModel : ObservableObject, IAccountCalend
set => SetProperty(AccountCalendar.IsPrimary, value, AccountCalendar, (u, i) => u.IsPrimary = i);
}
public bool IsReadOnly
{
get => AccountCalendar.IsReadOnly;
set => SetProperty(AccountCalendar.IsReadOnly, value, AccountCalendar, (u, i) => u.IsReadOnly = i);
}
public bool IsSynchronizationEnabled
{
get => AccountCalendar.IsSynchronizationEnabled;
@@ -440,6 +440,11 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
private async Task SaveAsync()
{
if (CurrentEvent == null) return;
if (CurrentEvent.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
try
{
@@ -506,6 +511,11 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
private async Task DeleteAsync()
{
if (CurrentEvent == null) return;
if (CurrentEvent.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
// If the event is a master recurring event, ask for confirmation
if (CurrentEvent.IsRecurringParent)
@@ -610,6 +620,11 @@ public partial class EventDetailsPageViewModel : CalendarBaseViewModel
private async Task SendRsvpResponse(AttendeeStatus status)
{
if (CurrentEvent == null) return;
if (CurrentEvent.AssignedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
try
{
@@ -16,6 +16,7 @@ public class AccountCalendar : IAccountCalendar
public string SynchronizationDeltaToken { get; set; }
public string Name { get; set; }
public bool IsPrimary { get; set; }
public bool IsReadOnly { get; set; }
public bool IsSynchronizationEnabled { get; set; } = true;
public bool IsExtended { get; set; } = true;
public CalendarItemShowAs DefaultShowAs { get; set; } = CalendarItemShowAs.Busy;
@@ -10,6 +10,7 @@ public interface IAccountCalendar
string TextColorHex { get; set; }
string BackgroundColorHex { get; set; }
bool IsPrimary { get; set; }
bool IsReadOnly { get; set; }
bool IsSynchronizationEnabled { get; set; }
Guid AccountId { get; set; }
string RemoteCalendarId { get; set; }
@@ -16,6 +16,7 @@ namespace Wino.Core.Domain.Interfaces;
public interface IMailDialogService : IDialogServiceBase
{
void ShowReadOnlyCalendarMessage();
Task<bool> ShowHardDeleteConfirmationAsync();
Task HandleSystemFolderConfigurationDialogAsync(Guid accountId, IFolderService folderService);
@@ -213,6 +213,8 @@
"CalendarEventDetails_Organizer": "Organizer",
"CalendarEventDetails_People": "People",
"CalendarEventDetails_ReadOnlyEvent": "Read-only event",
"CalendarReadOnly_Title": "Read-only calendar",
"CalendarReadOnly_Message": "You can't update this calendar or its events. This calendar is read-only.",
"CalendarContextMenu_Respond": "Respond",
"CalendarEventDetails_Reminder": "Reminder",
"CalendarReminder_StartedHoursAgo": "Started {0} hours ago",
@@ -145,6 +145,8 @@ public static class GoogleIntegratorExtensions
Id = Guid.NewGuid(),
TimeZone = calendarListEntry.TimeZone,
IsPrimary = calendarListEntry.Primary.GetValueOrDefault(),
IsReadOnly = !string.Equals(calendarListEntry.AccessRole, "owner", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(calendarListEntry.AccessRole, "writer", StringComparison.OrdinalIgnoreCase),
IsSynchronizationEnabled = true,
};
@@ -190,6 +190,7 @@ public static class OutlookIntegratorExtensions
Id = Guid.NewGuid(),
RemoteCalendarId = outlookCalendar.Id,
IsPrimary = outlookCalendar.IsDefaultCalendar.GetValueOrDefault(),
IsReadOnly = !outlookCalendar.CanEdit.GetValueOrDefault(true),
Name = outlookCalendar.Name,
IsSynchronizationEnabled = true,
IsExtended = true,
@@ -165,6 +165,13 @@ public class WinoRequestDelegator : IWinoRequestDelegator
if (calendarPreparationRequest == null)
return;
var resolvedCalendar = await ResolveCalendarAsync(calendarPreparationRequest).ConfigureAwait(false);
if (resolvedCalendar?.IsReadOnly == true)
{
_dialogService.ShowReadOnlyCalendarMessage();
return;
}
IRequestBase request = calendarPreparationRequest.Operation switch
{
CalendarSynchronizerOperation.CreateEvent => await CreateCalendarEventRequestAsync(calendarPreparationRequest).ConfigureAwait(false),
@@ -212,6 +219,25 @@ public class WinoRequestDelegator : IWinoRequestDelegator
return new CreateCalendarEventRequest(composeResult, assignedCalendar);
}
private async Task<AccountCalendar> ResolveCalendarAsync(CalendarOperationPreparationRequest calendarPreparationRequest)
{
if (calendarPreparationRequest.Operation == CalendarSynchronizerOperation.CreateEvent)
{
var calendarId = calendarPreparationRequest.ComposeResult?.CalendarId ?? Guid.Empty;
return calendarId == Guid.Empty
? null
: await _calendarService.GetAccountCalendarAsync(calendarId).ConfigureAwait(false);
}
if (calendarPreparationRequest.CalendarItem?.AssignedCalendar is AccountCalendar assignedCalendar)
return assignedCalendar;
var fallbackCalendarId = calendarPreparationRequest.CalendarItem?.CalendarId ?? Guid.Empty;
return fallbackCalendarId == Guid.Empty
? null
: await _calendarService.GetAccountCalendarAsync(fallbackCalendarId).ConfigureAwait(false);
}
private IRequestBase CreateDeclineRequest(CalendarItem calendarItem, string responseMessage)
{
// For Outlook accounts, declined events are deleted by the server after synchronization.
+6 -1
View File
@@ -759,6 +759,8 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
existingLocalCalendar.BackgroundColorHex = resolvedColor;
existingLocalCalendar.TextColorHex = ColorHelpers.GetReadableTextColorHex(existingLocalCalendar.BackgroundColorHex);
existingLocalCalendar.IsPrimary = string.Equals(existingLocalCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
existingLocalCalendar.IsReadOnly = !string.Equals(calendar.AccessRole, "owner", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(calendar.AccessRole, "writer", StringComparison.OrdinalIgnoreCase);
updatedCalendars.Add(existingLocalCalendar);
}
@@ -940,14 +942,17 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
var remoteBackgroundColor = ResolveSynchronizedCalendarBackgroundColor(GetRemoteGmailCalendarBackgroundColor(calendarListEntry), accountCalendar);
var remoteTextColor = ColorHelpers.GetReadableTextColorHex(remoteBackgroundColor);
var remoteIsPrimary = string.Equals(calendarListEntry.Id, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
var remoteIsReadOnly = !string.Equals(calendarListEntry.AccessRole, "owner", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(calendarListEntry.AccessRole, "writer", StringComparison.OrdinalIgnoreCase);
bool isNameChanged = !string.Equals(accountCalendar.Name, remoteCalendarName, StringComparison.OrdinalIgnoreCase);
bool isTimeZoneChanged = !string.Equals(accountCalendar.TimeZone, remoteTimeZone, StringComparison.OrdinalIgnoreCase);
bool isBackgroundColorChanged = !string.Equals(accountCalendar.BackgroundColorHex, remoteBackgroundColor, StringComparison.OrdinalIgnoreCase);
bool isTextColorChanged = !string.Equals(accountCalendar.TextColorHex, remoteTextColor, StringComparison.OrdinalIgnoreCase);
bool isPrimaryChanged = accountCalendar.IsPrimary != remoteIsPrimary;
bool isReadOnlyChanged = accountCalendar.IsReadOnly != remoteIsReadOnly;
return isNameChanged || isTimeZoneChanged || isBackgroundColorChanged || isTextColorChanged || isPrimaryChanged;
return isNameChanged || isTimeZoneChanged || isBackgroundColorChanged || isTextColorChanged || isPrimaryChanged || isReadOnlyChanged;
}
private static string GetRemoteGmailCalendarBackgroundColor(CalendarListEntry calendarListEntry)
@@ -2674,6 +2674,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
{
existingLocalCalendar.Name = calendar.Name;
existingLocalCalendar.IsPrimary = string.Equals(existingLocalCalendar.RemoteCalendarId, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
existingLocalCalendar.IsReadOnly = !calendar.CanEdit.GetValueOrDefault(true);
existingLocalCalendar.BackgroundColorHex = resolvedColor;
existingLocalCalendar.TextColorHex = ColorHelpers.GetReadableTextColorHex(existingLocalCalendar.BackgroundColorHex);
@@ -2712,12 +2713,14 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
var remoteCalendarName = calendar.Name;
var remoteBackgroundColor = ResolveSynchronizedCalendarBackgroundColor(GetRemoteOutlookCalendarBackgroundColor(calendar), accountCalendar);
var remoteIsPrimary = string.Equals(calendar.Id, remotePrimaryCalendarId, StringComparison.OrdinalIgnoreCase);
var remoteIsReadOnly = !calendar.CanEdit.GetValueOrDefault(true);
bool isNameChanged = !string.Equals(accountCalendar.Name, remoteCalendarName, StringComparison.OrdinalIgnoreCase);
bool isBackgroundColorChanged = !string.Equals(accountCalendar.BackgroundColorHex, remoteBackgroundColor, StringComparison.OrdinalIgnoreCase);
bool isPrimaryChanged = accountCalendar.IsPrimary != remoteIsPrimary;
bool isReadOnlyChanged = accountCalendar.IsReadOnly != remoteIsReadOnly;
return isNameChanged || isBackgroundColorChanged || isPrimaryChanged;
return isNameChanged || isBackgroundColorChanged || isPrimaryChanged || isReadOnlyChanged;
}
private static string GetRemoteOutlookCalendarBackgroundColor(Calendar calendar)
@@ -40,6 +40,12 @@ public class DialogService : DialogServiceBase, IMailDialogService
_winoAccountDataSyncService = winoAccountDataSyncService;
}
public void ShowReadOnlyCalendarMessage()
=> InfoBarMessage(
Translator.CalendarReadOnly_Title,
Translator.CalendarReadOnly_Message,
InfoBarMessageType.Warning);
public async Task<ICreateAccountAliasDialog> ShowCreateAccountAliasDialogAsync()
{
var createAccountAliasDialog = new CreateAccountAliasDialog()
+7
View File
@@ -168,6 +168,13 @@ public class DatabaseService : IDatabaseService
.ConfigureAwait(false);
}
if (!accountCalendarColumns.Any(c => c.Name == nameof(AccountCalendar.IsReadOnly)))
{
await Connection
.ExecuteAsync($"ALTER TABLE {nameof(AccountCalendar)} ADD COLUMN {nameof(AccountCalendar.IsReadOnly)} INTEGER NOT NULL DEFAULT 0")
.ConfigureAwait(false);
}
await Connection.ExecuteAsync("DROP TABLE IF EXISTS WinoAccountAddOnCache").ConfigureAwait(false);
}