Calendar improvements.
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
<PackageVersion Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
|
||||||
<PackageVersion Include="Google.Apis.Auth" Version="1.73.0" />
|
<PackageVersion Include="Google.Apis.Auth" Version="1.73.0" />
|
||||||
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.73.0.4063" />
|
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.73.0.4063" />
|
||||||
|
<PackageVersion Include="Google.Apis.Drive.v3" Version="1.73.0.4068" />
|
||||||
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.73.0.4029" />
|
<PackageVersion Include="Google.Apis.Gmail.v1" Version="1.73.0.4029" />
|
||||||
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.72.0.3973" />
|
<PackageVersion Include="Google.Apis.PeopleService.v1" Version="1.72.0.3973" />
|
||||||
<PackageVersion Include="HtmlKit" Version="1.2.0" />
|
<PackageVersion Include="HtmlKit" Version="1.2.0" />
|
||||||
@@ -74,4 +75,4 @@
|
|||||||
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
|
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
|
||||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -119,9 +119,13 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
|||||||
{
|
{
|
||||||
base.OnNavigatedTo(mode, parameters);
|
base.OnNavigatedTo(mode, parameters);
|
||||||
|
|
||||||
// Account list may have changed while this shell was inactive.
|
// Preserve the existing calendar shell frame state when the user switches
|
||||||
if (mode == NavigationMode.Back)
|
// between Mail and Calendar modes. Back/forward restoration should not
|
||||||
|
// force a new CalendarPage navigation, otherwise pages like
|
||||||
|
// CalendarEventComposePage get dropped from the inner frame stack.
|
||||||
|
if (mode != NavigationMode.New)
|
||||||
{
|
{
|
||||||
|
UpdateDateNavigationHeaderItems();
|
||||||
await InitializeAccountCalendarsAsync();
|
await InitializeAccountCalendarsAsync();
|
||||||
ValidateConfiguredNewEventCalendar();
|
ValidateConfiguredNewEventCalendar();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -102,8 +102,15 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
public CalendarSettings CurrentSettings { get; }
|
public CalendarSettings CurrentSettings { get; }
|
||||||
public string TimePickerClockIdentifier => CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "24HourClock" : "12HourClock";
|
public string TimePickerClockIdentifier => CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "24HourClock" : "12HourClock";
|
||||||
public bool HasAttachments => Attachments.Count > 0;
|
public bool HasAttachments => Attachments.Count > 0;
|
||||||
|
public bool IsSelectedCalendarCalDav => SelectedCalendar?.Account?.ProviderType == MailProviderType.IMAP4 &&
|
||||||
|
SelectedCalendar.Account.ServerInformation?.CalendarSupportMode == ImapCalendarSupportMode.CalDav;
|
||||||
|
public bool CanAddAttachments => !IsSelectedCalendarCalDav;
|
||||||
|
public string AttachmentsDisabledTooltipText => IsSelectedCalendarCalDav
|
||||||
|
? Translator.CalendarEventCompose_AttachmentsNotSupportedForCalDav
|
||||||
|
: string.Empty;
|
||||||
public string SelectedCalendarDisplayText => SelectedCalendar?.Name ?? Translator.CalendarEventCompose_SelectCalendar;
|
public string SelectedCalendarDisplayText => SelectedCalendar?.Name ?? Translator.CalendarEventCompose_SelectCalendar;
|
||||||
public string SelectedCalendarAccountText => SelectedCalendar?.Account?.Address ?? string.Empty;
|
public string SelectedCalendarAccountText => SelectedCalendar?.Account?.Address ?? string.Empty;
|
||||||
|
public bool IsDailyRecurrenceSelected => SelectedRecurrenceFrequencyOption?.Frequency == CalendarItemRecurrenceFrequency.Daily;
|
||||||
|
|
||||||
public CalendarEventComposePageViewModel(IAccountService accountService,
|
public CalendarEventComposePageViewModel(IAccountService accountService,
|
||||||
ICalendarService calendarService,
|
ICalendarService calendarService,
|
||||||
@@ -183,6 +190,15 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
|
|
||||||
SelectedShowAsOption = ShowAsOptions.FirstOrDefault(option => option.ShowAs == value.DefaultShowAs)
|
SelectedShowAsOption = ShowAsOptions.FirstOrDefault(option => option.ShowAs == value.DefaultShowAs)
|
||||||
?? ShowAsOptions.FirstOrDefault();
|
?? ShowAsOptions.FirstOrDefault();
|
||||||
|
|
||||||
|
if (IsSelectedCalendarCalDav && Attachments.Count > 0)
|
||||||
|
{
|
||||||
|
Attachments.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(IsSelectedCalendarCalDav));
|
||||||
|
OnPropertyChanged(nameof(CanAddAttachments));
|
||||||
|
OnPropertyChanged(nameof(AttachmentsDisabledTooltipText));
|
||||||
OnPropertyChanged(nameof(SelectedCalendarDisplayText));
|
OnPropertyChanged(nameof(SelectedCalendarDisplayText));
|
||||||
OnPropertyChanged(nameof(SelectedCalendarAccountText));
|
OnPropertyChanged(nameof(SelectedCalendarAccountText));
|
||||||
}
|
}
|
||||||
@@ -228,12 +244,19 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
UpdateRecurrenceSummary();
|
UpdateRecurrenceSummary();
|
||||||
}
|
}
|
||||||
partial void OnSelectedRecurrenceIntervalChanged(int value) => UpdateRecurrenceSummary();
|
partial void OnSelectedRecurrenceIntervalChanged(int value) => UpdateRecurrenceSummary();
|
||||||
partial void OnSelectedRecurrenceFrequencyOptionChanged(CalendarComposeFrequencyOption value) => UpdateRecurrenceSummary();
|
partial void OnSelectedRecurrenceFrequencyOptionChanged(CalendarComposeFrequencyOption value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(IsDailyRecurrenceSelected));
|
||||||
|
UpdateRecurrenceSummary();
|
||||||
|
}
|
||||||
partial void OnRecurrenceEndDateChanged(DateTimeOffset? value) => UpdateRecurrenceSummary();
|
partial void OnRecurrenceEndDateChanged(DateTimeOffset? value) => UpdateRecurrenceSummary();
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task AddAttachmentsAsync()
|
private async Task AddAttachmentsAsync()
|
||||||
{
|
{
|
||||||
|
if (!CanAddAttachments)
|
||||||
|
return;
|
||||||
|
|
||||||
var pickedFiles = await _dialogService.PickFilesMetadataAsync("*");
|
var pickedFiles = await _dialogService.PickFilesMetadataAsync("*");
|
||||||
if (pickedFiles.Count == 0)
|
if (pickedFiles.Count == 0)
|
||||||
return;
|
return;
|
||||||
@@ -471,7 +494,9 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
ShowAs = SelectedShowAsOption?.ShowAs ?? SelectedCalendar?.DefaultShowAs ?? CalendarItemShowAs.Busy,
|
ShowAs = SelectedShowAsOption?.ShowAs ?? SelectedCalendar?.DefaultShowAs ?? CalendarItemShowAs.Busy,
|
||||||
SelectedReminders = BuildSelectedReminders(),
|
SelectedReminders = BuildSelectedReminders(),
|
||||||
Attendees = BuildAttendees(uniqueAttendees),
|
Attendees = BuildAttendees(uniqueAttendees),
|
||||||
Attachments = Attachments.Select(attachment => attachment.ToDraftModel()).ToList(),
|
Attachments = CanAddAttachments
|
||||||
|
? Attachments.Select(attachment => attachment.ToDraftModel()).ToList()
|
||||||
|
: [],
|
||||||
Recurrence = BuildRecurrenceRule(),
|
Recurrence = BuildRecurrenceRule(),
|
||||||
RecurrenceSummary = RecurrenceSummary
|
RecurrenceSummary = RecurrenceSummary
|
||||||
};
|
};
|
||||||
@@ -527,10 +552,12 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
|
|
||||||
var effectiveStart = GetEffectiveStartDateTime();
|
var effectiveStart = GetEffectiveStartDateTime();
|
||||||
var effectiveEnd = GetEffectiveEndDateTime();
|
var effectiveEnd = GetEffectiveEndDateTime();
|
||||||
var selectedDays = WeekdayOptions
|
var selectedDays = IsDailyRecurrenceSelected
|
||||||
.Where(option => option.IsSelected)
|
? WeekdayOptions
|
||||||
.Select(option => option.DayOfWeek)
|
.Where(option => option.IsSelected)
|
||||||
.ToList();
|
.Select(option => option.DayOfWeek)
|
||||||
|
.ToList()
|
||||||
|
: [];
|
||||||
|
|
||||||
RecurrenceSummary = CalendarRecurrenceSummaryFormatter.BuildSummary(
|
RecurrenceSummary = CalendarRecurrenceSummaryFormatter.BuildSummary(
|
||||||
IsRecurring,
|
IsRecurring,
|
||||||
@@ -565,10 +592,12 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
$"INTERVAL={SelectedRecurrenceInterval}"
|
$"INTERVAL={SelectedRecurrenceInterval}"
|
||||||
};
|
};
|
||||||
|
|
||||||
var selectedDays = WeekdayOptions
|
var selectedDays = IsDailyRecurrenceSelected
|
||||||
.Where(option => option.IsSelected)
|
? WeekdayOptions
|
||||||
.Select(option => option.RuleValue)
|
.Where(option => option.IsSelected)
|
||||||
.ToList();
|
.Select(option => option.RuleValue)
|
||||||
|
.ToList()
|
||||||
|
: [];
|
||||||
|
|
||||||
if (selectedDays.Count > 0)
|
if (selectedDays.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -649,7 +678,8 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
|
|
||||||
private bool TryAddAttachment(string fileName, string filePath, string fileExtension, long size)
|
private bool TryAddAttachment(string fileName, string filePath, string fileExtension, long size)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(filePath) ||
|
if (!CanAddAttachments ||
|
||||||
|
string.IsNullOrWhiteSpace(filePath) ||
|
||||||
Attachments.Any(existing => existing.FilePath.Equals(filePath, StringComparison.OrdinalIgnoreCase)))
|
Attachments.Any(existing => existing.FilePath.Equals(filePath, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
"CalendarAttendeeStatus_Tentative": "Tentative",
|
"CalendarAttendeeStatus_Tentative": "Tentative",
|
||||||
"CalendarEventDetails_Attachments": "Attachments",
|
"CalendarEventDetails_Attachments": "Attachments",
|
||||||
"CalendarEventCompose_AddAttachment": "Add attachment",
|
"CalendarEventCompose_AddAttachment": "Add attachment",
|
||||||
|
"CalendarEventCompose_AttachmentsNotSupportedForCalDav": "Attachments are not supported for CalDAV calendars.",
|
||||||
"CalendarEventCompose_AllDay": "All Day",
|
"CalendarEventCompose_AllDay": "All Day",
|
||||||
"CalendarEventCompose_EndDate": "End date",
|
"CalendarEventCompose_EndDate": "End date",
|
||||||
"CalendarEventCompose_EndTime": "End time",
|
"CalendarEventCompose_EndTime": "End time",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@@ -9,12 +10,14 @@ using System.Web;
|
|||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Google;
|
using Google;
|
||||||
using Google.Apis.Calendar.v3.Data;
|
using Google.Apis.Calendar.v3.Data;
|
||||||
|
using Google.Apis.Drive.v3;
|
||||||
using Google.Apis.Gmail.v1;
|
using Google.Apis.Gmail.v1;
|
||||||
using Google.Apis.Gmail.v1.Data;
|
using Google.Apis.Gmail.v1.Data;
|
||||||
using Google.Apis.Http;
|
using Google.Apis.Http;
|
||||||
using Google.Apis.PeopleService.v1;
|
using Google.Apis.PeopleService.v1;
|
||||||
using Google.Apis.Requests;
|
using Google.Apis.Requests;
|
||||||
using Google.Apis.Services;
|
using Google.Apis.Services;
|
||||||
|
using Google.Apis.Upload;
|
||||||
using MailKit;
|
using MailKit;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
@@ -42,12 +45,15 @@ using Wino.Core.Requests.Mail;
|
|||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
using CalendarService = Google.Apis.Calendar.v3.CalendarService;
|
using CalendarService = Google.Apis.Calendar.v3.CalendarService;
|
||||||
|
using DriveFile = Google.Apis.Drive.v3.Data.File;
|
||||||
|
using DriveService = Google.Apis.Drive.v3.DriveService;
|
||||||
|
|
||||||
namespace Wino.Core.Synchronizers.Mail;
|
namespace Wino.Core.Synchronizers.Mail;
|
||||||
|
|
||||||
[JsonSerializable(typeof(Message))]
|
[JsonSerializable(typeof(Message))]
|
||||||
[JsonSerializable(typeof(Label))]
|
[JsonSerializable(typeof(Label))]
|
||||||
[JsonSerializable(typeof(Draft))]
|
[JsonSerializable(typeof(Draft))]
|
||||||
|
[JsonSerializable(typeof(Event))]
|
||||||
public partial class GmailSynchronizerJsonContext : JsonSerializerContext;
|
public partial class GmailSynchronizerJsonContext : JsonSerializerContext;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -88,6 +94,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
private readonly ConfigurableHttpClient _googleHttpClient;
|
private readonly ConfigurableHttpClient _googleHttpClient;
|
||||||
private readonly GmailService _gmailService;
|
private readonly GmailService _gmailService;
|
||||||
private readonly CalendarService _calendarService;
|
private readonly CalendarService _calendarService;
|
||||||
|
private readonly DriveService _driveService;
|
||||||
private readonly PeopleServiceService _peopleService;
|
private readonly PeopleServiceService _peopleService;
|
||||||
|
|
||||||
private readonly IGmailChangeProcessor _gmailChangeProcessor;
|
private readonly IGmailChangeProcessor _gmailChangeProcessor;
|
||||||
@@ -114,6 +121,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
_gmailService = new GmailService(initializer);
|
_gmailService = new GmailService(initializer);
|
||||||
_peopleService = new PeopleServiceService(initializer);
|
_peopleService = new PeopleServiceService(initializer);
|
||||||
_calendarService = new CalendarService(initializer);
|
_calendarService = new CalendarService(initializer);
|
||||||
|
_driveService = new DriveService(initializer);
|
||||||
|
|
||||||
_gmailChangeProcessor = gmailChangeProcessor;
|
_gmailChangeProcessor = gmailChangeProcessor;
|
||||||
_gmailSynchronizerErrorHandlerFactory = gmailSynchronizerErrorHandlerFactory;
|
_gmailSynchronizerErrorHandlerFactory = gmailSynchronizerErrorHandlerFactory;
|
||||||
@@ -1689,6 +1697,15 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
{
|
{
|
||||||
// TODO: Handle new Gmail Label added or updated.
|
// TODO: Handle new Gmail Label added or updated.
|
||||||
}
|
}
|
||||||
|
else if (bundle is HttpRequestBundle<IClientServiceRequest, Event> eventBundle && eventBundle.Request is CreateCalendarEventRequest createCalendarEventRequest)
|
||||||
|
{
|
||||||
|
var createdEvent = await eventBundle.DeserializeBundleAsync(httpResponseMessage, GmailSynchronizerJsonContext.Default.Event, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (createdEvent == null || string.IsNullOrWhiteSpace(createdEvent.Id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await UploadCalendarEventAttachmentsAsync(createCalendarEventRequest, createdEvent, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
else if (bundle is HttpRequestBundle<IClientServiceRequest, Draft> draftBundle && draftBundle.Request is CreateDraftRequest createDraftRequest)
|
else if (bundle is HttpRequestBundle<IClientServiceRequest, Draft> draftBundle && draftBundle.Request is CreateDraftRequest createDraftRequest)
|
||||||
{
|
{
|
||||||
// New draft mail is created.
|
// New draft mail is created.
|
||||||
@@ -2355,7 +2372,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
? Google.Apis.Calendar.v3.EventsResource.InsertRequest.SendUpdatesEnum.All
|
? Google.Apis.Calendar.v3.EventsResource.InsertRequest.SendUpdatesEnum.All
|
||||||
: Google.Apis.Calendar.v3.EventsResource.InsertRequest.SendUpdatesEnum.None;
|
: Google.Apis.Calendar.v3.EventsResource.InsertRequest.SendUpdatesEnum.None;
|
||||||
|
|
||||||
return [new HttpRequestBundle<IClientServiceRequest>(insertRequest, request)];
|
return [new HttpRequestBundle<IClientServiceRequest, Event>(insertRequest, request)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<IRequestBundle<IClientServiceRequest>> AcceptEvent(AcceptEventRequest request)
|
public override List<IRequestBundle<IClientServiceRequest>> AcceptEvent(AcceptEventRequest request)
|
||||||
@@ -2596,9 +2613,84 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
|||||||
_gmailService.Dispose();
|
_gmailService.Dispose();
|
||||||
_peopleService.Dispose();
|
_peopleService.Dispose();
|
||||||
_calendarService.Dispose();
|
_calendarService.Dispose();
|
||||||
|
_driveService.Dispose();
|
||||||
_googleHttpClient.Dispose();
|
_googleHttpClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UploadCalendarEventAttachmentsAsync(CreateCalendarEventRequest request, Event createdEvent, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var composeAttachments = request.ComposeResult.Attachments ?? [];
|
||||||
|
if (composeAttachments.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (composeAttachments.Count > 25)
|
||||||
|
throw new InvalidOperationException("Google Calendar supports at most 25 attachments per event.");
|
||||||
|
|
||||||
|
var eventAttachments = createdEvent.Attachments?
|
||||||
|
.Where(attachment => attachment != null && !string.IsNullOrWhiteSpace(attachment.FileUrl))
|
||||||
|
.ToList() ?? [];
|
||||||
|
|
||||||
|
foreach (var attachment in composeAttachments.Where(a => !string.IsNullOrWhiteSpace(a.FilePath) && File.Exists(a.FilePath)))
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
eventAttachments.Add(await UploadAttachmentToDriveAsync(attachment, cancellationToken).ConfigureAwait(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventAttachments.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var patchRequest = _calendarService.Events.Patch(new Event
|
||||||
|
{
|
||||||
|
Attachments = eventAttachments
|
||||||
|
}, request.AssignedCalendar.RemoteCalendarId, createdEvent.Id);
|
||||||
|
|
||||||
|
patchRequest.SupportsAttachments = true;
|
||||||
|
patchRequest.SendUpdates = Google.Apis.Calendar.v3.EventsResource.PatchRequest.SendUpdatesEnum.None;
|
||||||
|
|
||||||
|
await patchRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<EventAttachment> UploadAttachmentToDriveAsync(
|
||||||
|
Wino.Core.Domain.Models.Calendar.CalendarEventComposeAttachmentDraft attachment,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var fileName = string.IsNullOrWhiteSpace(attachment.FileName)
|
||||||
|
? Path.GetFileName(attachment.FilePath)
|
||||||
|
: attachment.FileName;
|
||||||
|
var contentType = MimeTypes.GetMimeType(fileName);
|
||||||
|
|
||||||
|
await using var fileStream = File.OpenRead(attachment.FilePath);
|
||||||
|
|
||||||
|
var uploadRequest = _driveService.Files.Create(new DriveFile
|
||||||
|
{
|
||||||
|
Name = fileName,
|
||||||
|
MimeType = contentType
|
||||||
|
}, fileStream, contentType);
|
||||||
|
uploadRequest.Fields = "id,name,mimeType,webViewLink";
|
||||||
|
|
||||||
|
var uploadProgress = await uploadRequest.UploadAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (uploadProgress.Status != UploadStatus.Completed)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Failed to upload '{fileName}' to Google Drive. Upload status: {uploadProgress.Status}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var uploadedFile = uploadRequest.ResponseBody;
|
||||||
|
if (uploadedFile == null || string.IsNullOrWhiteSpace(uploadedFile.Id) || string.IsNullOrWhiteSpace(uploadedFile.WebViewLink))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Google Drive did not return a valid attachment link for '{fileName}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EventAttachment
|
||||||
|
{
|
||||||
|
FileId = uploadedFile.Id,
|
||||||
|
FileUrl = uploadedFile.WebViewLink,
|
||||||
|
MimeType = uploadedFile.MimeType ?? contentType,
|
||||||
|
Title = uploadedFile.Name ?? fileName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static TimeSpan ResolveOffset(DateTime dateTime, string timeZoneId)
|
private static TimeSpan ResolveOffset(DateTime dateTime, string timeZoneId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(timeZoneId))
|
if (string.IsNullOrWhiteSpace(timeZoneId))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<PackageReference Include="CommunityToolkit.Diagnostics" />
|
<PackageReference Include="CommunityToolkit.Diagnostics" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
<PackageReference Include="Google.Apis.Calendar.v3" />
|
<PackageReference Include="Google.Apis.Calendar.v3" />
|
||||||
|
<PackageReference Include="Google.Apis.Drive.v3" />
|
||||||
<PackageReference Include="Google.Apis.Gmail.v1" />
|
<PackageReference Include="Google.Apis.Gmail.v1" />
|
||||||
<PackageReference Include="Google.Apis.PeopleService.v1" />
|
<PackageReference Include="Google.Apis.PeopleService.v1" />
|
||||||
<PackageReference Include="HtmlAgilityPack" />
|
<PackageReference Include="HtmlAgilityPack" />
|
||||||
@@ -43,4 +44,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Domain\Models\Errors\" />
|
<Folder Include="Domain\Models\Errors\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -327,8 +327,13 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="4"
|
Grid.Column="4"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Bind domain:Translator.CalendarEventCompose_ForWeekdays}" />
|
Text="{x:Bind domain:Translator.CalendarEventCompose_ForWeekdays}"
|
||||||
<ItemsControl Grid.Column="5" ItemsSource="{x:Bind ViewModel.WeekdayOptions}">
|
Visibility="{x:Bind ViewModel.IsDailyRecurrenceSelected, Mode=OneWay}" />
|
||||||
|
<ItemsControl
|
||||||
|
Grid.Column="5"
|
||||||
|
IsEnabled="{x:Bind ViewModel.IsDailyRecurrenceSelected, Mode=OneWay}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.WeekdayOptions}"
|
||||||
|
Visibility="{x:Bind ViewModel.IsDailyRecurrenceSelected, Mode=OneWay}">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
@@ -472,11 +477,13 @@
|
|||||||
DragLeave="AttachmentsPane_DragLeave"
|
DragLeave="AttachmentsPane_DragLeave"
|
||||||
DragOver="AttachmentsPane_DragOver"
|
DragOver="AttachmentsPane_DragOver"
|
||||||
Drop="AttachmentsPane_Drop"
|
Drop="AttachmentsPane_Drop"
|
||||||
|
ToolTipService.ToolTip="{x:Bind ViewModel.AttachmentsDisabledTooltipText, Mode=OneWay}"
|
||||||
Visibility="{x:Bind AttachmentsToggle.IsChecked, Mode=OneWay}">
|
Visibility="{x:Bind AttachmentsToggle.IsChecked, Mode=OneWay}">
|
||||||
<StackPanel Spacing="8">
|
<StackPanel Spacing="8">
|
||||||
<Button
|
<Button
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Command="{x:Bind ViewModel.AddAttachmentsCommand}"
|
Command="{x:Bind ViewModel.AddAttachmentsCommand}"
|
||||||
|
IsEnabled="{x:Bind ViewModel.CanAddAttachments, Mode=OneWay}"
|
||||||
Style="{StaticResource TransparentActionButtonStyle}">
|
Style="{StaticResource TransparentActionButtonStyle}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<coreControls:WinoFontIcon FontSize="14" Icon="AttachmentNew" />
|
<coreControls:WinoFontIcon FontSize="14" Icon="AttachmentNew" />
|
||||||
|
|||||||
@@ -150,6 +150,12 @@ public sealed partial class CalendarEventComposePage : CalendarEventComposePageA
|
|||||||
|
|
||||||
private void AttachmentsPane_DragOver(object sender, DragEventArgs e)
|
private void AttachmentsPane_DragOver(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (!ViewModel.CanAddAttachments)
|
||||||
|
{
|
||||||
|
e.AcceptedOperation = DataPackageOperation.None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.AcceptedOperation = e.DataView.Contains(StandardDataFormats.StorageItems)
|
e.AcceptedOperation = e.DataView.Contains(StandardDataFormats.StorageItems)
|
||||||
? DataPackageOperation.Copy
|
? DataPackageOperation.Copy
|
||||||
: DataPackageOperation.None;
|
: DataPackageOperation.None;
|
||||||
@@ -169,7 +175,7 @@ public sealed partial class CalendarEventComposePage : CalendarEventComposePageA
|
|||||||
|
|
||||||
private async void AttachmentsPane_Drop(object sender, DragEventArgs e)
|
private async void AttachmentsPane_Drop(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
if (!e.DataView.Contains(StandardDataFormats.StorageItems))
|
if (!ViewModel.CanAddAttachments || !e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,6 @@
|
|||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
x:Name="EditorThemeToggleButton"
|
x:Name="EditorThemeToggleButton"
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=TwoWay}"
|
IsChecked="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=TwoWay}"
|
||||||
Label=""
|
|
||||||
ToolTipService.ToolTip="Toggle editor dark mode">
|
ToolTipService.ToolTip="Toggle editor dark mode">
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TrimmerRootAssembly Include="Google.Apis.Auth" />
|
<TrimmerRootAssembly Include="Google.Apis.Auth" />
|
||||||
|
<TrimmerRootAssembly Include="Google.Apis.Drive.v3" />
|
||||||
<TrimmerRootAssembly Include="Google.Apis.Gmail.v1" />
|
<TrimmerRootAssembly Include="Google.Apis.Gmail.v1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user