mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 00:46:12 +02:00
Add meeting countdown and agenda plugins
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
using DesktopMagic.Api;
|
||||
using DesktopMagic.Api.Settings;
|
||||
using DesktopMagic.Helpers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Text;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DesktopMagic.BuiltInPlugins;
|
||||
|
||||
internal class AgendaPlugin : AsyncPlugin
|
||||
{
|
||||
[Setting("agenda-calendar-url", "iCal URL")]
|
||||
private readonly TextBox calendarUrl = new TextBox(string.Empty);
|
||||
|
||||
[Setting("agenda-refresh-button", "Refresh Calendar")]
|
||||
private readonly Button refreshButton = new Button("Refresh");
|
||||
|
||||
[Setting("agenda-refresh-interval", "Refresh Every (minutes)")]
|
||||
private readonly IntegerUpDown refreshMinutes = new IntegerUpDown(1, 120, 10);
|
||||
|
||||
[Setting("agenda-lookahead-hours", "Look Ahead (hours)")]
|
||||
private readonly IntegerUpDown lookAheadHours = new IntegerUpDown(1, 336, 24);
|
||||
|
||||
[Setting("agenda-max-events", "Maximum Events")]
|
||||
private readonly IntegerUpDown maxEvents = new IntegerUpDown(1, 15, 5);
|
||||
|
||||
[Setting("agenda-include-all-day", "Include All-Day Events")]
|
||||
private readonly CheckBox includeAllDayEvents = new CheckBox(true);
|
||||
|
||||
[Setting("agenda-show-date", "Show Date")]
|
||||
private readonly CheckBox showDate = new CheckBox(true);
|
||||
|
||||
public override int UpdateInterval { get; set; } = 30000;
|
||||
|
||||
private List<CalendarEventEntry> upcomingEvents = [];
|
||||
private DateTime nextRefreshTime = DateTime.MinValue;
|
||||
private bool forceRefresh = true;
|
||||
private bool themeChanged = true;
|
||||
private string? errorText;
|
||||
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
refreshButton.OnClick += () =>
|
||||
{
|
||||
forceRefresh = true;
|
||||
Application.UpdateWindow();
|
||||
};
|
||||
|
||||
calendarUrl.OnValueChanged += MarkForRefresh;
|
||||
refreshMinutes.OnValueChanged += MarkForRefresh;
|
||||
lookAheadHours.OnValueChanged += MarkForRefresh;
|
||||
maxEvents.OnValueChanged += MarkForRefresh;
|
||||
includeAllDayEvents.OnValueChanged += MarkForRefresh;
|
||||
showDate.OnValueChanged += Application.UpdateWindow;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task<Bitmap?> MainAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (themeChanged || forceRefresh || DateTime.Now >= nextRefreshTime)
|
||||
{
|
||||
await RefreshEventsAsync(cancellationToken);
|
||||
themeChanged = false;
|
||||
}
|
||||
|
||||
return RenderAgendaBitmap();
|
||||
}
|
||||
|
||||
public override void OnThemeChanged()
|
||||
{
|
||||
themeChanged = true;
|
||||
}
|
||||
|
||||
public override void OnSettingsChanged()
|
||||
{
|
||||
Application.UpdateWindow();
|
||||
}
|
||||
|
||||
private void MarkForRefresh()
|
||||
{
|
||||
forceRefresh = true;
|
||||
Application.UpdateWindow();
|
||||
}
|
||||
|
||||
private async Task RefreshEventsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
forceRefresh = false;
|
||||
nextRefreshTime = DateTime.Now.AddMinutes(Math.Max(1, refreshMinutes.Value));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(calendarUrl.Value))
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = "Set iCal URL in settings";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
DateTime windowEnd = now.AddHours(Math.Max(1, lookAheadHours.Value));
|
||||
|
||||
upcomingEvents = await IcsCalendarHelper.GetUpcomingEventsAsync(
|
||||
calendarUrl.Value.Trim(),
|
||||
now,
|
||||
windowEnd,
|
||||
maxEvents.Value,
|
||||
includeAllDayEvents.Value,
|
||||
cancellationToken);
|
||||
|
||||
errorText = null;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = $"Calendar unavailable: {ex.Message}";
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = $"Calendar unavailable: {ex.Message}";
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = $"Calendar unavailable: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap RenderAgendaBitmap()
|
||||
{
|
||||
List<(string Day, string Date, string Time, string Title, bool IsMessage)> rows = [];
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(errorText))
|
||||
{
|
||||
rows.Add((string.Empty, string.Empty, string.Empty, errorText, true));
|
||||
}
|
||||
else if (upcomingEvents.Count == 0)
|
||||
{
|
||||
rows.Add((string.Empty, string.Empty, string.Empty, "No events in selected window", true));
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (CalendarEventEntry eventEntry in upcomingEvents)
|
||||
{
|
||||
string dayPart = showDate.Value ? eventEntry.StartLocal.ToString("ddd") : string.Empty;
|
||||
string datePart = showDate.Value ? eventEntry.StartLocal.ToString("dd MMM yyyy") : string.Empty;
|
||||
string timePart = eventEntry.IsAllDay ? "All day" : eventEntry.StartLocal.ToShortTimeString();
|
||||
|
||||
rows.Add((dayPart, datePart, timePart, eventEntry.Title, false));
|
||||
}
|
||||
}
|
||||
|
||||
using Font font = new Font(Application.Theme.Font, 70);
|
||||
using Bitmap measureBitmap = new Bitmap(1, 1);
|
||||
using Graphics measureGraphics = Graphics.FromImage(measureBitmap);
|
||||
measureGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
|
||||
|
||||
bool hasDayColumn = rows.Any(row => !row.IsMessage && !string.IsNullOrWhiteSpace(row.Day));
|
||||
bool hasDateColumn = rows.Any(row => !row.IsMessage && !string.IsNullOrWhiteSpace(row.Date));
|
||||
bool hasTimeColumn = rows.Any(row => !row.IsMessage && !string.IsNullOrWhiteSpace(row.Time));
|
||||
float maxDayWidth = 0;
|
||||
float maxDateWidth = 0;
|
||||
float maxTimeWidth = 0;
|
||||
float maxTitleWidth = 0;
|
||||
float lineHeight = 0;
|
||||
|
||||
foreach ((string day, string date, string time, string title, bool isMessage) in rows)
|
||||
{
|
||||
if (isMessage)
|
||||
{
|
||||
SizeF messageSize = measureGraphics.MeasureString(title, font);
|
||||
maxTitleWidth = Math.Max(maxTitleWidth, messageSize.Width);
|
||||
lineHeight = Math.Max(lineHeight, messageSize.Height);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasDayColumn)
|
||||
{
|
||||
SizeF daySize = measureGraphics.MeasureString(day, font);
|
||||
maxDayWidth = Math.Max(maxDayWidth, daySize.Width);
|
||||
lineHeight = Math.Max(lineHeight, daySize.Height);
|
||||
}
|
||||
|
||||
if (hasDateColumn)
|
||||
{
|
||||
SizeF dateSize = measureGraphics.MeasureString(date, font);
|
||||
maxDateWidth = Math.Max(maxDateWidth, dateSize.Width);
|
||||
lineHeight = Math.Max(lineHeight, dateSize.Height);
|
||||
}
|
||||
|
||||
if (hasTimeColumn)
|
||||
{
|
||||
SizeF timeSize = measureGraphics.MeasureString(time, font);
|
||||
maxTimeWidth = Math.Max(maxTimeWidth, timeSize.Width);
|
||||
lineHeight = Math.Max(lineHeight, timeSize.Height);
|
||||
}
|
||||
|
||||
SizeF titleSize = measureGraphics.MeasureString(title, font);
|
||||
maxTitleWidth = Math.Max(maxTitleWidth, titleSize.Width);
|
||||
lineHeight = Math.Max(lineHeight, titleSize.Height);
|
||||
}
|
||||
|
||||
float columnSpacing = measureGraphics.MeasureString(" ", font).Width;
|
||||
|
||||
float prefixWidth = 0;
|
||||
bool hasAnyPrefix = false;
|
||||
if (hasDayColumn)
|
||||
{
|
||||
prefixWidth += maxDayWidth;
|
||||
hasAnyPrefix = true;
|
||||
}
|
||||
if (hasDateColumn)
|
||||
{
|
||||
prefixWidth += (hasAnyPrefix ? columnSpacing : 0) + maxDateWidth;
|
||||
hasAnyPrefix = true;
|
||||
}
|
||||
if (hasTimeColumn)
|
||||
{
|
||||
prefixWidth += (hasAnyPrefix ? columnSpacing : 0) + maxTimeWidth;
|
||||
hasAnyPrefix = true;
|
||||
}
|
||||
|
||||
float totalWidth = (hasAnyPrefix ? prefixWidth + columnSpacing : 0) + maxTitleWidth;
|
||||
|
||||
int width = Math.Max(1, (int)Math.Ceiling(totalWidth));
|
||||
int height = Math.Max(1, (int)Math.Ceiling(lineHeight * rows.Count));
|
||||
|
||||
Bitmap bitmap = new Bitmap(width, height);
|
||||
bitmap.SetResolution(100, 100);
|
||||
|
||||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||||
using SolidBrush brush = new SolidBrush(Application.Theme.PrimaryColor);
|
||||
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
|
||||
graphics.Clear(Color.Transparent);
|
||||
|
||||
float dayColumnX = 0;
|
||||
float dateColumnX = hasDayColumn ? dayColumnX + maxDayWidth + columnSpacing : dayColumnX;
|
||||
float timeColumnX = hasDateColumn ? dateColumnX + maxDateWidth + columnSpacing : dateColumnX;
|
||||
float titleColumnX = hasAnyPrefix ? timeColumnX + (hasTimeColumn ? maxTimeWidth : 0) + columnSpacing : 0;
|
||||
|
||||
for (int index = 0; index < rows.Count; index++)
|
||||
{
|
||||
(string day, string date, string time, string title, bool isMessage) = rows[index];
|
||||
float y = index * lineHeight;
|
||||
|
||||
if (isMessage)
|
||||
{
|
||||
graphics.DrawString(title, font, brush, 0, y);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasDayColumn && !string.IsNullOrWhiteSpace(day))
|
||||
{
|
||||
graphics.DrawString(day, font, brush, dayColumnX, y);
|
||||
}
|
||||
|
||||
if (hasDateColumn && !string.IsNullOrWhiteSpace(date))
|
||||
{
|
||||
graphics.DrawString(date, font, brush, dateColumnX, y);
|
||||
}
|
||||
|
||||
if (hasTimeColumn && !string.IsNullOrWhiteSpace(time))
|
||||
{
|
||||
graphics.DrawString(time, font, brush, timeColumnX, y);
|
||||
}
|
||||
|
||||
graphics.DrawString(title, font, brush, titleColumnX, y);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
using DesktopMagic.Api;
|
||||
using DesktopMagic.Api.Settings;
|
||||
using DesktopMagic.Helpers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Text;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DesktopMagic.BuiltInPlugins;
|
||||
|
||||
internal class NextMeetingCountdownPlugin : AsyncPlugin
|
||||
{
|
||||
[Setting("meeting-calendar-url", "iCal URL")]
|
||||
private readonly TextBox calendarUrl = new TextBox(string.Empty);
|
||||
|
||||
[Setting("meeting-refresh-button", "Refresh Calendar")]
|
||||
private readonly Button refreshButton = new Button("Refresh");
|
||||
|
||||
[Setting("meeting-refresh-interval", "Refresh Every (minutes)")]
|
||||
private readonly IntegerUpDown refreshMinutes = new IntegerUpDown(1, 120, 10);
|
||||
|
||||
[Setting("meeting-lookahead-hours", "Look Ahead (hours)")]
|
||||
private readonly IntegerUpDown lookAheadHours = new IntegerUpDown(1, 336, 48);
|
||||
|
||||
[Setting("meeting-include-all-day", "Include All-Day Events")]
|
||||
private readonly CheckBox includeAllDayEvents = new CheckBox(false);
|
||||
|
||||
[Setting("meeting-time-display", "Start Time Format")]
|
||||
private readonly ComboBox startTimeDisplay = new ComboBox("None", "Date and time", "Time only");
|
||||
|
||||
[Setting("meeting-show-location", "Show Location")]
|
||||
private readonly CheckBox showLocation = new CheckBox(false);
|
||||
|
||||
[Setting("meeting-center-lines", "Center Lines")]
|
||||
private readonly CheckBox centerLines = new CheckBox(false);
|
||||
|
||||
public override int UpdateInterval { get; set; } = 1000;
|
||||
|
||||
private List<CalendarEventEntry> upcomingEvents = [];
|
||||
private DateTime nextRefreshTime = DateTime.MinValue;
|
||||
private bool forceRefresh = true;
|
||||
private bool themeChanged = true;
|
||||
private string? errorText;
|
||||
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
refreshButton.OnClick += () =>
|
||||
{
|
||||
forceRefresh = true;
|
||||
Application.UpdateWindow();
|
||||
};
|
||||
|
||||
calendarUrl.OnValueChanged += MarkForRefresh;
|
||||
refreshMinutes.OnValueChanged += MarkForRefresh;
|
||||
lookAheadHours.OnValueChanged += MarkForRefresh;
|
||||
includeAllDayEvents.OnValueChanged += MarkForRefresh;
|
||||
startTimeDisplay.OnValueChanged += Application.UpdateWindow;
|
||||
showLocation.OnValueChanged += Application.UpdateWindow;
|
||||
centerLines.OnValueChanged += Application.UpdateWindow;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task<Bitmap?> MainAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (themeChanged || forceRefresh || DateTime.Now >= nextRefreshTime)
|
||||
{
|
||||
await RefreshEventsAsync(cancellationToken);
|
||||
themeChanged = false;
|
||||
}
|
||||
|
||||
return RenderCountdownBitmap();
|
||||
}
|
||||
|
||||
public override void OnThemeChanged()
|
||||
{
|
||||
themeChanged = true;
|
||||
}
|
||||
|
||||
public override void OnSettingsChanged()
|
||||
{
|
||||
Application.UpdateWindow();
|
||||
}
|
||||
|
||||
private void MarkForRefresh()
|
||||
{
|
||||
forceRefresh = true;
|
||||
Application.UpdateWindow();
|
||||
}
|
||||
|
||||
private async Task RefreshEventsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
forceRefresh = false;
|
||||
nextRefreshTime = DateTime.Now.AddMinutes(Math.Max(1, refreshMinutes.Value));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(calendarUrl.Value))
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = "Set iCal URL in settings";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
DateTime windowEnd = now.AddHours(Math.Max(1, lookAheadHours.Value));
|
||||
|
||||
upcomingEvents = await IcsCalendarHelper.GetUpcomingEventsAsync(
|
||||
calendarUrl.Value.Trim(),
|
||||
now,
|
||||
windowEnd,
|
||||
20,
|
||||
includeAllDayEvents.Value,
|
||||
cancellationToken);
|
||||
|
||||
errorText = null;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = $"Calendar unavailable: {ex.Message}";
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = $"Calendar unavailable: {ex.Message}";
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
upcomingEvents = [];
|
||||
errorText = $"Calendar unavailable: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap RenderCountdownBitmap()
|
||||
{
|
||||
List<string> lines = [];
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(errorText))
|
||||
{
|
||||
lines.Add(errorText);
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
CalendarEventEntry? nextEvent = upcomingEvents.FirstOrDefault(eventEntry => eventEntry.EndLocal > now);
|
||||
|
||||
if (nextEvent is null)
|
||||
{
|
||||
lines.Add("No upcoming meetings");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add(nextEvent.Title);
|
||||
|
||||
if (startTimeDisplay.Value != "None")
|
||||
{
|
||||
string timeText = nextEvent.IsAllDay
|
||||
? "All day"
|
||||
: startTimeDisplay.Value == "Time only"
|
||||
? nextEvent.StartLocal.ToString("t")
|
||||
: $"{nextEvent.StartLocal:D} {nextEvent.StartLocal:t}";
|
||||
lines.Add(timeText);
|
||||
}
|
||||
|
||||
if (showLocation.Value && !string.IsNullOrWhiteSpace(nextEvent.Location))
|
||||
{
|
||||
lines.Add(nextEvent.Location!);
|
||||
}
|
||||
|
||||
if (now < nextEvent.StartLocal)
|
||||
{
|
||||
lines.Add($"Starts in {FormatCountdown(nextEvent.StartLocal - now)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add("In progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using Font font = new Font(Application.Theme.Font, 90);
|
||||
using Bitmap measureBitmap = new Bitmap(1, 1);
|
||||
using Graphics measureGraphics = Graphics.FromImage(measureBitmap);
|
||||
measureGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
|
||||
|
||||
float maxWidth = 0;
|
||||
float lineHeight = 0;
|
||||
foreach (string line in lines)
|
||||
{
|
||||
SizeF size = measureGraphics.MeasureString(line, font);
|
||||
maxWidth = Math.Max(maxWidth, size.Width);
|
||||
lineHeight = Math.Max(lineHeight, size.Height);
|
||||
}
|
||||
|
||||
int width = Math.Max(1, (int)Math.Ceiling(maxWidth));
|
||||
int height = Math.Max(1, (int)Math.Ceiling(lineHeight * lines.Count));
|
||||
|
||||
Bitmap bitmap = new Bitmap(width, height);
|
||||
bitmap.SetResolution(100, 100);
|
||||
|
||||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||||
using SolidBrush brush = new SolidBrush(Application.Theme.PrimaryColor);
|
||||
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
|
||||
graphics.Clear(Color.Transparent);
|
||||
|
||||
for (int index = 0; index < lines.Count; index++)
|
||||
{
|
||||
string line = lines[index];
|
||||
float x = 0;
|
||||
if (centerLines.Value)
|
||||
{
|
||||
SizeF lineSize = graphics.MeasureString(line, font);
|
||||
x = Math.Max(0, (width - lineSize.Width) / 2f);
|
||||
}
|
||||
|
||||
graphics.DrawString(line, font, brush, x, index * lineHeight);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static string FormatCountdown(TimeSpan timeSpan)
|
||||
{
|
||||
int totalHours = (int)timeSpan.TotalHours;
|
||||
return $"{totalHours:00}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}";
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@
|
||||
<PackageReference Include="BusyIndicators" Version="2.1.3" />
|
||||
<PackageReference Include="CuteUtils" Version="1.0.0" />
|
||||
<PackageReference Include="Interop.IWshRuntimeLibrary" Version="1.0.1" />
|
||||
<PackageReference Include="Ical.Net" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
|
||||
<PackageReference Include="Modio" Version="1.0.0" />
|
||||
<PackageReference Include="NAudio" Version="2.2.1" />
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
using Ical.Net;
|
||||
using Ical.Net.CalendarComponents;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DesktopMagic.Helpers;
|
||||
|
||||
public sealed class CalendarEventEntry
|
||||
{
|
||||
public string Title { get; init; } = "Untitled";
|
||||
public string? Location { get; init; }
|
||||
public DateTime StartLocal { get; init; }
|
||||
public DateTime EndLocal { get; init; }
|
||||
public bool IsAllDay { get; init; }
|
||||
}
|
||||
|
||||
public static class IcsCalendarHelper
|
||||
{
|
||||
private static readonly HttpClient httpClient = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(12)
|
||||
};
|
||||
|
||||
static IcsCalendarHelper()
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("DesktopMagic");
|
||||
httpClient.DefaultRequestHeaders.Accept.ParseAdd("text/calendar");
|
||||
}
|
||||
|
||||
public static async Task<List<CalendarEventEntry>> GetUpcomingEventsAsync(
|
||||
string calendarUrl,
|
||||
DateTime windowStartLocal,
|
||||
DateTime windowEndLocal,
|
||||
int maxEvents,
|
||||
bool includeAllDayEvents,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using HttpResponseMessage response = await httpClient.GetAsync(calendarUrl, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string icsContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
Calendar calendar = Calendar.Load(icsContent);
|
||||
DateTime windowStartUtc = windowStartLocal.ToUniversalTime();
|
||||
DateTime windowEndUtc = windowEndLocal.ToUniversalTime();
|
||||
|
||||
List<CalendarEventEntry> results = [];
|
||||
|
||||
foreach (var occurrence in calendar.GetOccurrences(windowStartUtc, windowEndUtc))
|
||||
{
|
||||
if (occurrence.Source is not CalendarEvent calendarEvent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DateTime startLocal = occurrence.Period.StartTime.AsSystemLocal;
|
||||
DateTime endLocal = occurrence.Period.EndTime?.AsSystemLocal ?? startLocal.AddHours(1);
|
||||
bool isAllDay = !occurrence.Period.StartTime.HasTime;
|
||||
|
||||
if (!includeAllDayEvents && isAllDay)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (endLocal <= windowStartLocal)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(new CalendarEventEntry
|
||||
{
|
||||
Title = string.IsNullOrWhiteSpace(calendarEvent.Summary) ? "Untitled" : calendarEvent.Summary,
|
||||
Location = string.IsNullOrWhiteSpace(calendarEvent.Location) ? null : calendarEvent.Location,
|
||||
StartLocal = startLocal,
|
||||
EndLocal = endLocal,
|
||||
IsAllDay = isAllDay
|
||||
});
|
||||
}
|
||||
|
||||
return results
|
||||
.OrderBy(eventEntry => eventEntry.StartLocal)
|
||||
.Take(Math.Max(1, maxEvents))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,8 @@ public sealed class Manager
|
||||
{new((string)App.LanguageDictionary["date"],3) { Author = "Stone_Red" }, typeof(DatePlugin)},
|
||||
{new((string)App.LanguageDictionary["cpuUsage"], 4) { Author = "Stone_Red" }, typeof(CpuMonitorPlugin)},
|
||||
{new((string)App.LanguageDictionary["weather"], 5) { Author = "Stone_Red" }, typeof(WeatherPlugin)},
|
||||
{new((string)App.LanguageDictionary["nextMeetingCountdown"], 6) { Author = "Stone_Red" }, typeof(NextMeetingCountdownPlugin)},
|
||||
{new((string)App.LanguageDictionary["agenda"], 7) { Author = "Stone_Red" }, typeof(AgendaPlugin)},
|
||||
};
|
||||
|
||||
// Window management
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
<system:String x:Key="cpuUsage">CPU Auslastung</system:String>
|
||||
<system:String x:Key="musicVisualizer">Musik Visualisierer</system:String>
|
||||
<system:String x:Key="weather">Wetter</system:String>
|
||||
<system:String x:Key="nextMeetingCountdown">Nächstes Meeting Countdown</system:String>
|
||||
<system:String x:Key="agenda">Agenda</system:String>
|
||||
<system:String x:Key="amplifier">Signalverstärkung:</system:String>
|
||||
<system:String x:Key="newLayout">Neues Layout</system:String>
|
||||
<system:String x:Key="deleteLayout">Layout Löschen</system:String>
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
<system:String x:Key="cpuUsage">CPU Usage</system:String>
|
||||
<system:String x:Key="musicVisualizer">Music Visualizer</system:String>
|
||||
<system:String x:Key="weather">Weather</system:String>
|
||||
<system:String x:Key="nextMeetingCountdown">Next Meeting Countdown</system:String>
|
||||
<system:String x:Key="agenda">Agenda</system:String>
|
||||
<system:String x:Key="amplifier">Signal Amplification:</system:String>
|
||||
<system:String x:Key="newLayout">New Layout</system:String>
|
||||
<system:String x:Key="deleteLayout">Delete Layout</system:String>
|
||||
|
||||
Reference in New Issue
Block a user