using System; 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) }; private static readonly LunarCalendarService LunarCalendarService = new(); private static readonly string[] ZhWeekdayHeaders = ["日", "一", "二", "三", "四", "五", "六"]; private static readonly string[] EnWeekdayHeaders = ["S", "M", "T", "W", "T", "F", "S"]; private TimeZoneService? _timeZoneService; private double _currentCellSize = 64; private double _calendarDayFontSize = 14; private double _calendarTodayDotSize = 28; public DateWidget() { InitializeComponent(); _timer.Tick += OnTimerTick; AttachedToVisualTree += OnAttachedToVisualTree; DetachedFromVisualTree += OnDetachedFromVisualTree; SizeChanged += OnSizeChanged; 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(); } private void OnSizeChanged(object? sender, SizeChangedEventArgs e) { ApplyCellSize(_currentCellSize); } 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; 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); GenerateCalendar(now); } 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]; } private void GenerateCalendar(DateTime currentDate) { var removeList = new List(); foreach (var child in CalendarGrid.Children) { if (child is Control control && control.Tag is string tag && (tag == "day" || tag == "today-dot")) { removeList.Add(control); } } foreach (var child in removeList) { CalendarGrid.Children.Remove(child); } var year = currentDate.Year; var month = currentDate.Month; var today = currentDate.Day; var firstDayOfMonth = new DateTime(year, month, 1); var daysInMonth = DateTime.DaysInMonth(year, month); var startDayOfWeek = (int)firstDayOfMonth.DayOfWeek; for (var day = 1; day <= daysInMonth; day++) { var row = (day + startDayOfWeek - 1) / 7; var col = (day + startDayOfWeek - 1) % 7; if (row > 4) { continue; } var dayText = new TextBlock { Text = day.ToString(CultureInfo.CurrentCulture), HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, FontSize = _calendarDayFontSize, FontWeight = FontWeight.SemiBold, Tag = "day" }; if (day == today) { var accentBrush = this.TryFindResource("AdaptiveAccentBrush", out var accent) ? accent as IBrush : Brushes.Blue; var onAccentBrush = this.TryFindResource("AdaptiveOnAccentBrush", out var onAccent) ? onAccent as IBrush : Brushes.White; dayText.Foreground = onAccentBrush; var dot = new Border { Width = _calendarTodayDotSize, Height = _calendarTodayDotSize, CornerRadius = new CornerRadius(_calendarTodayDotSize * 0.5), Background = accentBrush, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, Child = dayText, Tag = "today-dot" }; Grid.SetRow(dot, row); Grid.SetColumn(dot, col); CalendarGrid.Children.Add(dot); } else { var isWeekend = col is 0 or 6; dayText.Foreground = isWeekend ? GetThemeBrush("AdaptiveTextSecondaryBrush", 0.82) : GetThemeBrush("AdaptiveTextPrimaryBrush", 0.92); Grid.SetRow(dayText, row); Grid.SetColumn(dayText, col); CalendarGrid.Children.Add(dayText); } } } public void ApplyCellSize(double cellSize) { _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); } }