Files
LanMountainDesktop/LanMontainDesktop/Views/Components/DateWidget.axaml.cs

278 lines
9.6 KiB
C#
Raw Normal View History

2026-03-02 20:02:14 +08:00
using System;
2026-03-01 16:50:06 +08:00
using System.Collections.Generic;
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMontainDesktop.Services;
namespace LanMontainDesktop.Views.Components;
public partial class DateWidget : UserControl
{
private readonly DispatcherTimer _timer = new()
{
Interval = TimeSpan.FromMinutes(1)
};
2026-03-02 20:02:14 +08:00
private static readonly LunarCalendarService LunarCalendarService = new();
private static readonly string[] ZhWeekdayHeaders = ["日", "一", "二", "三", "四", "五", "六"];
private static readonly string[] EnWeekdayHeaders = ["S", "M", "T", "W", "T", "F", "S"];
2026-03-01 16:50:06 +08:00
private TimeZoneService? _timeZoneService;
2026-03-02 20:02:14 +08:00
private double _currentCellSize = 64;
private double _calendarDayFontSize = 14;
private double _calendarTodayDotSize = 28;
2026-03-01 16:50:06 +08:00
public DateWidget()
{
InitializeComponent();
_timer.Tick += OnTimerTick;
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
2026-03-02 20:02:14 +08:00
SizeChanged += OnSizeChanged;
2026-03-01 16:50:06 +08:00
UpdateDate();
}
public void SetTimeZoneService(TimeZoneService timeZoneService)
{
if (_timeZoneService != null)
{
_timeZoneService.TimeZoneChanged -= OnTimeZoneChanged;
}
_timeZoneService = timeZoneService;
_timeZoneService.TimeZoneChanged += OnTimeZoneChanged;
UpdateDate();
}
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
UpdateDate();
_timer.Start();
}
private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
_timer.Stop();
}
2026-03-02 20:02:14 +08:00
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
{
ApplyCellSize(_currentCellSize);
}
2026-03-01 16:50:06 +08:00
private void OnTimerTick(object? sender, EventArgs e)
{
UpdateDate();
}
private void OnTimeZoneChanged(object? sender, EventArgs e)
{
UpdateDate();
}
private void UpdateDate()
{
var now = _timeZoneService?.GetCurrentTime() ?? DateTime.Now;
var culture = CultureInfo.CurrentCulture;
2026-03-02 20:02:14 +08:00
var isZh = culture.TwoLetterISOLanguageName.Equals("zh", StringComparison.OrdinalIgnoreCase);
var lunar = LunarCalendarService.GetLunarInfo(now);
GregorianHeadlineTextBlock.Text = isZh
? $"{now.Month}月{now.Day}日 {ToChineseWeekday(now.DayOfWeek)}"
: now.ToString("MMM d ddd", culture);
if (isZh)
{
LunarDateTextBlock.Text = $"农历 {lunar.LunarDateZh}";
LunarMetaTextBlock.Text = $"{lunar.GanzhiYearZh}年({lunar.ZodiacZh}年)";
YiLabelTextBlock.Text = "宜";
JiLabelTextBlock.Text = "忌";
YiItemsTextBlock.Text = "祭祀 祈福 出行 会友";
JiItemsTextBlock.Text = "动土 诉讼 远航 争执";
}
else
{
LunarDateTextBlock.Text = $"Lunar {lunar.LunarDateEn}";
LunarMetaTextBlock.Text = $"Ganzhi year: {lunar.GanzhiYearEn} ({lunar.ZodiacEn})";
YiLabelTextBlock.Text = "Do";
JiLabelTextBlock.Text = "Avoid";
YiItemsTextBlock.Text = "Worship Blessing Travel Meet";
JiItemsTextBlock.Text = "Groundwork Lawsuit Voyage Dispute";
}
UpdateWeekdayHeaders(isZh);
2026-03-01 16:50:06 +08:00
GenerateCalendar(now);
}
2026-03-02 20:02:14 +08:00
private static string ToChineseWeekday(DayOfWeek dayOfWeek)
{
return dayOfWeek switch
{
DayOfWeek.Sunday => "周日",
DayOfWeek.Monday => "周一",
DayOfWeek.Tuesday => "周二",
DayOfWeek.Wednesday => "周三",
DayOfWeek.Thursday => "周四",
DayOfWeek.Friday => "周五",
_ => "周六"
};
}
private void UpdateWeekdayHeaders(bool isZh)
{
var headers = isZh ? ZhWeekdayHeaders : EnWeekdayHeaders;
WeekdayText0.Text = headers[0];
WeekdayText1.Text = headers[1];
WeekdayText2.Text = headers[2];
WeekdayText3.Text = headers[3];
WeekdayText4.Text = headers[4];
WeekdayText5.Text = headers[5];
WeekdayText6.Text = headers[6];
}
2026-03-01 16:50:06 +08:00
private void GenerateCalendar(DateTime currentDate)
{
2026-03-02 20:02:14 +08:00
var removeList = new List<Control>();
2026-03-01 16:50:06 +08:00
foreach (var child in CalendarGrid.Children)
{
2026-03-02 20:02:14 +08:00
if (child is Control control && control.Tag is string tag &&
(tag == "day" || tag == "today-dot"))
2026-03-01 16:50:06 +08:00
{
2026-03-02 20:02:14 +08:00
removeList.Add(control);
2026-03-01 16:50:06 +08:00
}
}
2026-03-02 20:02:14 +08:00
foreach (var child in removeList)
2026-03-01 16:50:06 +08:00
{
CalendarGrid.Children.Remove(child);
}
var year = currentDate.Year;
var month = currentDate.Month;
var today = currentDate.Day;
2026-03-02 20:02:14 +08:00
2026-03-01 16:50:06 +08:00
var firstDayOfMonth = new DateTime(year, month, 1);
var daysInMonth = DateTime.DaysInMonth(year, month);
2026-03-02 20:02:14 +08:00
var startDayOfWeek = (int)firstDayOfMonth.DayOfWeek;
2026-03-01 16:50:06 +08:00
2026-03-02 20:02:14 +08:00
for (var day = 1; day <= daysInMonth; day++)
2026-03-01 16:50:06 +08:00
{
2026-03-02 20:02:14 +08:00
var row = (day + startDayOfWeek - 1) / 7;
2026-03-01 16:50:06 +08:00
var col = (day + startDayOfWeek - 1) % 7;
2026-03-02 20:02:14 +08:00
if (row > 4)
{
continue;
}
2026-03-01 16:50:06 +08:00
var dayText = new TextBlock
{
2026-03-02 20:02:14 +08:00
Text = day.ToString(CultureInfo.CurrentCulture),
2026-03-01 16:50:06 +08:00
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
2026-03-02 20:02:14 +08:00
FontSize = _calendarDayFontSize,
FontWeight = FontWeight.SemiBold,
2026-03-01 16:50:06 +08:00
Tag = "day"
};
if (day == today)
{
2026-03-02 20:02:14 +08:00
var accentBrush = this.TryFindResource("AdaptiveAccentBrush", out var accent)
? accent as IBrush
2026-03-01 16:50:06 +08:00
: Brushes.Blue;
2026-03-02 20:02:14 +08:00
var onAccentBrush = this.TryFindResource("AdaptiveOnAccentBrush", out var onAccent)
? onAccent as IBrush
2026-03-01 16:50:06 +08:00
: Brushes.White;
2026-03-02 20:02:14 +08:00
2026-03-01 16:50:06 +08:00
dayText.Foreground = onAccentBrush;
2026-03-02 20:02:14 +08:00
var dot = new Border
2026-03-01 16:50:06 +08:00
{
2026-03-02 20:02:14 +08:00
Width = _calendarTodayDotSize,
Height = _calendarTodayDotSize,
CornerRadius = new CornerRadius(_calendarTodayDotSize * 0.5),
2026-03-01 16:50:06 +08:00
Background = accentBrush,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
2026-03-02 20:02:14 +08:00
Child = dayText,
Tag = "today-dot"
2026-03-01 16:50:06 +08:00
};
2026-03-02 20:02:14 +08:00
Grid.SetRow(dot, row);
Grid.SetColumn(dot, col);
CalendarGrid.Children.Add(dot);
2026-03-01 16:50:06 +08:00
}
else
{
2026-03-02 20:02:14 +08:00
var isWeekend = col is 0 or 6;
dayText.Foreground = isWeekend
? GetThemeBrush("AdaptiveTextSecondaryBrush", 0.82)
: GetThemeBrush("AdaptiveTextPrimaryBrush", 0.92);
2026-03-01 16:50:06 +08:00
Grid.SetRow(dayText, row);
Grid.SetColumn(dayText, col);
CalendarGrid.Children.Add(dayText);
}
}
}
public void ApplyCellSize(double cellSize)
{
2026-03-02 20:02:14 +08:00
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(28 * scale, 16, 40));
RootBorder.Padding = new Thickness(Math.Clamp(12 * scale, 8, 18));
LeftPanelGrid.RowSpacing = Math.Clamp(8 * scale, 5, 14);
RightPanelGrid.RowSpacing = Math.Clamp(10 * scale, 6, 16);
LunarCardBorder.CornerRadius = new CornerRadius(Math.Clamp(24 * scale, 14, 34));
LunarCardBorder.Padding = new Thickness(Math.Clamp(14 * scale, 9, 20));
GregorianHeadlineTextBlock.FontSize = Math.Clamp(22 * scale, 14, 34);
WeekdayText0.FontSize = Math.Clamp(13 * scale, 9, 18);
WeekdayText1.FontSize = WeekdayText0.FontSize;
WeekdayText2.FontSize = WeekdayText0.FontSize;
WeekdayText3.FontSize = WeekdayText0.FontSize;
WeekdayText4.FontSize = WeekdayText0.FontSize;
WeekdayText5.FontSize = WeekdayText0.FontSize;
WeekdayText6.FontSize = WeekdayText0.FontSize;
LunarDateTextBlock.FontSize = Math.Clamp(28 * scale, 17, 44);
LunarMetaTextBlock.FontSize = Math.Clamp(14 * scale, 10, 22);
YiLabelTextBlock.FontSize = Math.Clamp(18 * scale, 12, 28);
JiLabelTextBlock.FontSize = YiLabelTextBlock.FontSize;
YiItemsTextBlock.FontSize = Math.Clamp(16 * scale, 11, 24);
JiItemsTextBlock.FontSize = YiItemsTextBlock.FontSize;
_calendarDayFontSize = Math.Clamp(14 * scale, 9, 22);
_calendarTodayDotSize = Math.Clamp(28 * scale, 17, 38);
UpdateDate();
}
private double ResolveScale()
{
var cellScale = Math.Clamp(_currentCellSize / 48d, 0.72, 1.55);
var heightScale = Bounds.Height > 1 ? Math.Clamp(Bounds.Height / 220d, 0.65, 1.65) : 1;
var widthScale = Bounds.Width > 1 ? Math.Clamp(Bounds.Width / 460d, 0.65, 1.65) : 1;
return Math.Clamp(Math.Min(cellScale, Math.Min(heightScale, widthScale) * 1.08), 0.65, 1.6);
}
private IBrush GetThemeBrush(string key, double opacity)
{
if (this.TryFindResource(key, out var value) && value is IBrush brush)
{
if (brush is ISolidColorBrush solid)
{
return new SolidColorBrush(solid.Color, opacity);
}
return brush;
}
return new SolidColorBrush(Colors.Gray, opacity);
2026-03-01 16:50:06 +08:00
}
}