mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 17:24:27 +08:00
feat.完善了时钟轻应用,为启动器提供了多语言支持
This commit is contained in:
@@ -1097,6 +1097,40 @@
|
||||
"clock.settings.second_mode_label": "Second Hand",
|
||||
"clock.second_mode.tick": "Tick",
|
||||
"clock.second_mode.sweep": "Sweep",
|
||||
"clockairapp.title": "Clock",
|
||||
"clockairapp.subtitle": "World clock, stopwatch and timer",
|
||||
"clockairapp.tab.world": "World",
|
||||
"clockairapp.tab.stopwatch": "Stopwatch",
|
||||
"clockairapp.tab.timer": "Timer",
|
||||
"clockairapp.tab.settings": "Settings",
|
||||
"clockairapp.world.local": "Local time",
|
||||
"clockairapp.world.add": "Add",
|
||||
"clockairapp.world.search": "Search city or time zone",
|
||||
"clockairapp.world.count": "{0} cities",
|
||||
"clockairapp.action.start": "Start",
|
||||
"clockairapp.action.pause": "Pause",
|
||||
"clockairapp.action.reset": "Reset",
|
||||
"clockairapp.action.remove": "Remove",
|
||||
"clockairapp.action.move_up": "Move up",
|
||||
"clockairapp.action.move_down": "Move down",
|
||||
"clockairapp.stopwatch.hint": "Lap timing stays in this window session.",
|
||||
"clockairapp.stopwatch.lap": "Lap",
|
||||
"clockairapp.stopwatch.lap_format": "Lap {0} {1}",
|
||||
"clockairapp.timer.hint": "Choose a preset or enter custom minutes.",
|
||||
"clockairapp.timer.apply": "Apply",
|
||||
"clockairapp.timer.minutes": "Minutes",
|
||||
"clockairapp.timer.finished": "Timer finished",
|
||||
"clockairapp.timer.duration_status": "Duration {0}",
|
||||
"clockairapp.timer.invalid": "Enter a valid minute value.",
|
||||
"clockairapp.settings.title": "Clock settings",
|
||||
"clockairapp.settings.time_format": "Time format",
|
||||
"clockairapp.settings.startup_tab": "Startup page",
|
||||
"clockairapp.settings.show_seconds": "Show seconds",
|
||||
"clockairapp.settings.activate_timer": "Activate window when timer finishes",
|
||||
"clockairapp.settings.time_format.system": "Follow system",
|
||||
"clockairapp.settings.time_format.24h": "24-hour",
|
||||
"clockairapp.settings.time_format.12h": "12-hour",
|
||||
"clockairapp.settings.startup.last": "Last used",
|
||||
"poetry.widget.loading_content": "Loading poetry...",
|
||||
"poetry.widget.loading_author": "Loading...",
|
||||
"poetry.widget.fetch_failed": "Poetry fetch failed",
|
||||
|
||||
@@ -811,6 +811,40 @@
|
||||
"desktop_clock.settings.second_mode_label": "秒針",
|
||||
"clock.second_mode.tick": "ティック",
|
||||
"clock.second_mode.sweep": "スイープ",
|
||||
"clockairapp.title": "時計",
|
||||
"clockairapp.subtitle": "世界時計、ストップウォッチ、タイマー",
|
||||
"clockairapp.tab.world": "世界時計",
|
||||
"clockairapp.tab.stopwatch": "ストップウォッチ",
|
||||
"clockairapp.tab.timer": "タイマー",
|
||||
"clockairapp.tab.settings": "設定",
|
||||
"clockairapp.world.local": "ローカル時刻",
|
||||
"clockairapp.world.add": "追加",
|
||||
"clockairapp.world.search": "都市またはタイムゾーンを検索",
|
||||
"clockairapp.world.count": "{0} 都市",
|
||||
"clockairapp.action.start": "開始",
|
||||
"clockairapp.action.pause": "一時停止",
|
||||
"clockairapp.action.reset": "リセット",
|
||||
"clockairapp.action.remove": "削除",
|
||||
"clockairapp.action.move_up": "上へ",
|
||||
"clockairapp.action.move_down": "下へ",
|
||||
"clockairapp.stopwatch.hint": "ラップは現在のウィンドウセッション内に保持されます。",
|
||||
"clockairapp.stopwatch.lap": "ラップ",
|
||||
"clockairapp.stopwatch.lap_format": "ラップ {0} {1}",
|
||||
"clockairapp.timer.hint": "プリセットを選ぶか、分数を入力します。",
|
||||
"clockairapp.timer.apply": "適用",
|
||||
"clockairapp.timer.minutes": "分",
|
||||
"clockairapp.timer.finished": "タイマー終了",
|
||||
"clockairapp.timer.duration_status": "時間 {0}",
|
||||
"clockairapp.timer.invalid": "有効な分数を入力してください。",
|
||||
"clockairapp.settings.title": "時計設定",
|
||||
"clockairapp.settings.time_format": "時刻形式",
|
||||
"clockairapp.settings.startup_tab": "起動ページ",
|
||||
"clockairapp.settings.show_seconds": "秒を表示",
|
||||
"clockairapp.settings.activate_timer": "タイマー終了時にウィンドウを前面へ",
|
||||
"clockairapp.settings.time_format.system": "システムに従う",
|
||||
"clockairapp.settings.time_format.24h": "24時間",
|
||||
"clockairapp.settings.time_format.12h": "12時間",
|
||||
"clockairapp.settings.startup.last": "前回使用",
|
||||
"poetry.widget.loading_content": "詩を読み込み中...",
|
||||
"poetry.widget.loading_author": "読み込み中...",
|
||||
"poetry.widget.fetch_failed": "詩の取得に失敗しました",
|
||||
|
||||
@@ -857,6 +857,40 @@
|
||||
"desktop_clock.settings.second_mode_label": "초침 방식",
|
||||
"clock.second_mode.tick": "똑딱이",
|
||||
"clock.second_mode.sweep": "스윕",
|
||||
"clockairapp.title": "시계",
|
||||
"clockairapp.subtitle": "세계 시계, 스톱워치, 타이머",
|
||||
"clockairapp.tab.world": "세계 시계",
|
||||
"clockairapp.tab.stopwatch": "스톱워치",
|
||||
"clockairapp.tab.timer": "타이머",
|
||||
"clockairapp.tab.settings": "설정",
|
||||
"clockairapp.world.local": "현지 시간",
|
||||
"clockairapp.world.add": "추가",
|
||||
"clockairapp.world.search": "도시 또는 시간대 검색",
|
||||
"clockairapp.world.count": "{0}개 도시",
|
||||
"clockairapp.action.start": "시작",
|
||||
"clockairapp.action.pause": "일시정지",
|
||||
"clockairapp.action.reset": "초기화",
|
||||
"clockairapp.action.remove": "삭제",
|
||||
"clockairapp.action.move_up": "위로",
|
||||
"clockairapp.action.move_down": "아래로",
|
||||
"clockairapp.stopwatch.hint": "랩 기록은 현재 창 세션에만 유지됩니다.",
|
||||
"clockairapp.stopwatch.lap": "랩",
|
||||
"clockairapp.stopwatch.lap_format": "랩 {0} {1}",
|
||||
"clockairapp.timer.hint": "프리셋을 선택하거나 사용자 지정 분을 입력하세요.",
|
||||
"clockairapp.timer.apply": "적용",
|
||||
"clockairapp.timer.minutes": "분",
|
||||
"clockairapp.timer.finished": "타이머 종료",
|
||||
"clockairapp.timer.duration_status": "시간 {0}",
|
||||
"clockairapp.timer.invalid": "올바른 분 값을 입력하세요.",
|
||||
"clockairapp.settings.title": "시계 설정",
|
||||
"clockairapp.settings.time_format": "시간 형식",
|
||||
"clockairapp.settings.startup_tab": "시작 페이지",
|
||||
"clockairapp.settings.show_seconds": "초 표시",
|
||||
"clockairapp.settings.activate_timer": "타이머 종료 시 창 활성화",
|
||||
"clockairapp.settings.time_format.system": "시스템 설정 따르기",
|
||||
"clockairapp.settings.time_format.24h": "24시간",
|
||||
"clockairapp.settings.time_format.12h": "12시간",
|
||||
"clockairapp.settings.startup.last": "마지막 사용",
|
||||
"poetry.widget.loading_content": "시 불러오는 중",
|
||||
"poetry.widget.loading_author": "로딩 중",
|
||||
"poetry.widget.fetch_failed": "시 가져오기 실패",
|
||||
|
||||
@@ -1027,6 +1027,40 @@
|
||||
"clock.settings.second_mode_label": "秒针方式",
|
||||
"clock.second_mode.tick": "跳针",
|
||||
"clock.second_mode.sweep": "扫针",
|
||||
"clockairapp.title": "时钟",
|
||||
"clockairapp.subtitle": "世界时钟、秒表和计时器",
|
||||
"clockairapp.tab.world": "世界时钟",
|
||||
"clockairapp.tab.stopwatch": "秒表",
|
||||
"clockairapp.tab.timer": "计时器",
|
||||
"clockairapp.tab.settings": "设置",
|
||||
"clockairapp.world.local": "本地时间",
|
||||
"clockairapp.world.add": "添加",
|
||||
"clockairapp.world.search": "搜索城市或时区",
|
||||
"clockairapp.world.count": "{0} 个城市",
|
||||
"clockairapp.action.start": "开始",
|
||||
"clockairapp.action.pause": "暂停",
|
||||
"clockairapp.action.reset": "重置",
|
||||
"clockairapp.action.remove": "移除",
|
||||
"clockairapp.action.move_up": "上移",
|
||||
"clockairapp.action.move_down": "下移",
|
||||
"clockairapp.stopwatch.hint": "计次记录仅保留在当前窗口会话中。",
|
||||
"clockairapp.stopwatch.lap": "计次",
|
||||
"clockairapp.stopwatch.lap_format": "计次 {0} {1}",
|
||||
"clockairapp.timer.hint": "选择预设时长,或输入自定义分钟数。",
|
||||
"clockairapp.timer.apply": "应用",
|
||||
"clockairapp.timer.minutes": "分钟",
|
||||
"clockairapp.timer.finished": "计时结束",
|
||||
"clockairapp.timer.duration_status": "时长 {0}",
|
||||
"clockairapp.timer.invalid": "请输入有效的分钟数。",
|
||||
"clockairapp.settings.title": "时钟设置",
|
||||
"clockairapp.settings.time_format": "时间格式",
|
||||
"clockairapp.settings.startup_tab": "启动页面",
|
||||
"clockairapp.settings.show_seconds": "显示秒数",
|
||||
"clockairapp.settings.activate_timer": "计时结束时激活窗口",
|
||||
"clockairapp.settings.time_format.system": "跟随系统",
|
||||
"clockairapp.settings.time_format.24h": "24 小时制",
|
||||
"clockairapp.settings.time_format.12h": "12 小时制",
|
||||
"clockairapp.settings.startup.last": "上次使用",
|
||||
"poetry.widget.loading_content": "正在加载诗词",
|
||||
"poetry.widget.loading_author": "加载中",
|
||||
"poetry.widget.fetch_failed": "诗词获取失败",
|
||||
|
||||
@@ -64,6 +64,11 @@ internal sealed class AirAppLauncherService : IAirAppLauncherService
|
||||
internal static string BuildSingleInstanceKey(string appId, string? sourceComponentId, string? sourcePlacementId)
|
||||
{
|
||||
var normalizedAppId = string.IsNullOrWhiteSpace(appId) ? "unknown" : appId.Trim();
|
||||
if (string.Equals(normalizedAppId, WorldClockAppId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return $"{normalizedAppId}:clock-suite:global";
|
||||
}
|
||||
|
||||
var normalizedComponentId = string.IsNullOrWhiteSpace(sourceComponentId) ? "none" : sourceComponentId.Trim();
|
||||
var normalizedPlacementId = string.IsNullOrWhiteSpace(sourcePlacementId) ? "none" : sourcePlacementId.Trim();
|
||||
return $"{normalizedAppId}:{normalizedComponentId}:{normalizedPlacementId}";
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public sealed class ClockAirAppSettingsSnapshot
|
||||
{
|
||||
public string TimeFormatMode { get; set; } = ClockAirAppTimeFormatMode.System;
|
||||
|
||||
public bool ShowSeconds { get; set; } = true;
|
||||
|
||||
public string StartupTab { get; set; } = ClockAirAppTabIds.Last;
|
||||
|
||||
public string LastSelectedTab { get; set; } = ClockAirAppTabIds.WorldClock;
|
||||
|
||||
public bool ActivateOnTimerFinished { get; set; } = true;
|
||||
|
||||
public List<string> WorldClockTimeZoneIds { get; set; } =
|
||||
[
|
||||
"China Standard Time",
|
||||
"GMT Standard Time",
|
||||
"AUS Eastern Standard Time",
|
||||
"Eastern Standard Time"
|
||||
];
|
||||
|
||||
public ClockAirAppSettingsSnapshot Clone()
|
||||
{
|
||||
return new ClockAirAppSettingsSnapshot
|
||||
{
|
||||
TimeFormatMode = ClockAirAppTimeFormatMode.Normalize(TimeFormatMode),
|
||||
ShowSeconds = ShowSeconds,
|
||||
StartupTab = ClockAirAppTabIds.Normalize(StartupTab, ClockAirAppTabIds.Last),
|
||||
LastSelectedTab = ClockAirAppTabIds.Normalize(LastSelectedTab),
|
||||
ActivateOnTimerFinished = ActivateOnTimerFinished,
|
||||
WorldClockTimeZoneIds = WorldClockTimeZoneIds is { Count: > 0 }
|
||||
? new List<string>(WorldClockTimeZoneIds.Where(static id => !string.IsNullOrWhiteSpace(id)).Select(static id => id.Trim()))
|
||||
: []
|
||||
};
|
||||
}
|
||||
|
||||
public static ClockAirAppSettingsSnapshot Normalize(ClockAirAppSettingsSnapshot? snapshot)
|
||||
{
|
||||
var normalized = (snapshot ?? new ClockAirAppSettingsSnapshot()).Clone();
|
||||
if (normalized.WorldClockTimeZoneIds.Count == 0)
|
||||
{
|
||||
normalized.WorldClockTimeZoneIds =
|
||||
[
|
||||
"China Standard Time",
|
||||
"GMT Standard Time",
|
||||
"AUS Eastern Standard Time",
|
||||
"Eastern Standard Time"
|
||||
];
|
||||
}
|
||||
|
||||
normalized.WorldClockTimeZoneIds = normalized.WorldClockTimeZoneIds
|
||||
.Select(static id => WorldClockTimeZoneCatalog.ResolveTimeZoneOrLocal(id).Id)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public sealed class ClockAirAppSettingsStore
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
private readonly string _settingsPath;
|
||||
|
||||
public ClockAirAppSettingsStore()
|
||||
: this(Path.Combine(AppDataPathProvider.GetDataRoot(), "AirApps", "Clock", "settings.json"))
|
||||
{
|
||||
}
|
||||
|
||||
public ClockAirAppSettingsStore(string settingsPath)
|
||||
{
|
||||
_settingsPath = settingsPath;
|
||||
}
|
||||
|
||||
public string SettingsPath => _settingsPath;
|
||||
|
||||
public ClockAirAppSettingsSnapshot Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_settingsPath))
|
||||
{
|
||||
return ClockAirAppSettingsSnapshot.Normalize(null);
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(_settingsPath);
|
||||
var snapshot = JsonSerializer.Deserialize<ClockAirAppSettingsSnapshot>(json, SerializerOptions);
|
||||
return ClockAirAppSettingsSnapshot.Normalize(snapshot);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("ClockAirApp", $"Failed to load clock Air APP settings from '{_settingsPath}'.", ex);
|
||||
return ClockAirAppSettingsSnapshot.Normalize(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(ClockAirAppSettingsSnapshot snapshot)
|
||||
{
|
||||
var normalized = ClockAirAppSettingsSnapshot.Normalize(snapshot);
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(_settingsPath);
|
||||
if (!string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.WriteAllText(_settingsPath, JsonSerializer.Serialize(normalized, SerializerOptions));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("ClockAirApp", $"Failed to save clock Air APP settings to '{_settingsPath}'.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public sealed class ClockAirAppStopwatchState
|
||||
{
|
||||
private readonly List<TimeSpan> _laps = [];
|
||||
private TimeSpan _elapsedBeforeRun = TimeSpan.Zero;
|
||||
private DateTimeOffset? _startedAt;
|
||||
|
||||
public bool IsRunning => _startedAt.HasValue;
|
||||
|
||||
public IReadOnlyList<TimeSpan> Laps => _laps;
|
||||
|
||||
public TimeSpan GetElapsed(DateTimeOffset now)
|
||||
{
|
||||
return _startedAt.HasValue
|
||||
? _elapsedBeforeRun + (now - _startedAt.Value)
|
||||
: _elapsedBeforeRun;
|
||||
}
|
||||
|
||||
public void StartOrResume(DateTimeOffset now)
|
||||
{
|
||||
if (_startedAt.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_startedAt = now;
|
||||
}
|
||||
|
||||
public void Pause(DateTimeOffset now)
|
||||
{
|
||||
if (!_startedAt.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_elapsedBeforeRun = GetElapsed(now);
|
||||
_startedAt = null;
|
||||
}
|
||||
|
||||
public TimeSpan AddLap(DateTimeOffset now)
|
||||
{
|
||||
var elapsed = GetElapsed(now);
|
||||
_laps.Insert(0, elapsed);
|
||||
if (_laps.Count > 50)
|
||||
{
|
||||
_laps.RemoveRange(50, _laps.Count - 50);
|
||||
}
|
||||
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_elapsedBeforeRun = TimeSpan.Zero;
|
||||
_startedAt = null;
|
||||
_laps.Clear();
|
||||
}
|
||||
}
|
||||
23
LanMountainDesktop/Services/ClockAirApp/ClockAirAppTabIds.cs
Normal file
23
LanMountainDesktop/Services/ClockAirApp/ClockAirAppTabIds.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public static class ClockAirAppTabIds
|
||||
{
|
||||
public const string Last = "last";
|
||||
public const string WorldClock = "world";
|
||||
public const string Stopwatch = "stopwatch";
|
||||
public const string Timer = "timer";
|
||||
public const string Settings = "settings";
|
||||
|
||||
public static string Normalize(string? value, string fallback = WorldClock)
|
||||
{
|
||||
return value?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
Last => Last,
|
||||
WorldClock => WorldClock,
|
||||
Stopwatch => Stopwatch,
|
||||
Timer => Timer,
|
||||
Settings => Settings,
|
||||
_ => fallback
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public static class ClockAirAppTimeFormatMode
|
||||
{
|
||||
public const string System = "system";
|
||||
public const string TwentyFourHour = "24h";
|
||||
public const string TwelveHour = "12h";
|
||||
|
||||
public static string Normalize(string? value)
|
||||
{
|
||||
return value?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
TwentyFourHour => TwentyFourHour,
|
||||
TwelveHour => TwelveHour,
|
||||
_ => System
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public static class ClockAirAppTimeFormatter
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> CityNames =
|
||||
new Dictionary<string, IReadOnlyDictionary<string, string>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["zh-CN"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["China Standard Time"] = "北京",
|
||||
["Asia/Shanghai"] = "北京",
|
||||
["GMT Standard Time"] = "伦敦",
|
||||
["Europe/London"] = "伦敦",
|
||||
["AUS Eastern Standard Time"] = "悉尼",
|
||||
["Australia/Sydney"] = "悉尼",
|
||||
["Eastern Standard Time"] = "纽约",
|
||||
["America/New_York"] = "纽约",
|
||||
["Tokyo Standard Time"] = "东京",
|
||||
["Asia/Tokyo"] = "东京",
|
||||
["UTC"] = "UTC",
|
||||
["Etc/UTC"] = "UTC"
|
||||
},
|
||||
["en-US"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["China Standard Time"] = "Beijing",
|
||||
["Asia/Shanghai"] = "Beijing",
|
||||
["GMT Standard Time"] = "London",
|
||||
["Europe/London"] = "London",
|
||||
["AUS Eastern Standard Time"] = "Sydney",
|
||||
["Australia/Sydney"] = "Sydney",
|
||||
["Eastern Standard Time"] = "New York",
|
||||
["America/New_York"] = "New York",
|
||||
["Tokyo Standard Time"] = "Tokyo",
|
||||
["Asia/Tokyo"] = "Tokyo",
|
||||
["UTC"] = "UTC",
|
||||
["Etc/UTC"] = "UTC"
|
||||
},
|
||||
["ja-JP"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["China Standard Time"] = "北京",
|
||||
["Asia/Shanghai"] = "北京",
|
||||
["GMT Standard Time"] = "ロンドン",
|
||||
["Europe/London"] = "ロンドン",
|
||||
["AUS Eastern Standard Time"] = "シドニー",
|
||||
["Australia/Sydney"] = "シドニー",
|
||||
["Eastern Standard Time"] = "ニューヨーク",
|
||||
["America/New_York"] = "ニューヨーク",
|
||||
["Tokyo Standard Time"] = "東京",
|
||||
["Asia/Tokyo"] = "東京",
|
||||
["UTC"] = "UTC",
|
||||
["Etc/UTC"] = "UTC"
|
||||
},
|
||||
["ko-KR"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["China Standard Time"] = "베이징",
|
||||
["Asia/Shanghai"] = "베이징",
|
||||
["GMT Standard Time"] = "런던",
|
||||
["Europe/London"] = "런던",
|
||||
["AUS Eastern Standard Time"] = "시드니",
|
||||
["Australia/Sydney"] = "시드니",
|
||||
["Eastern Standard Time"] = "뉴욕",
|
||||
["America/New_York"] = "뉴욕",
|
||||
["Tokyo Standard Time"] = "도쿄",
|
||||
["Asia/Tokyo"] = "도쿄",
|
||||
["UTC"] = "UTC",
|
||||
["Etc/UTC"] = "UTC"
|
||||
}
|
||||
};
|
||||
|
||||
public static string FormatTime(DateTime time, ClockAirAppSettingsSnapshot settings, CultureInfo culture)
|
||||
{
|
||||
var use24Hour = UseTwentyFourHourClock(settings.TimeFormatMode, culture);
|
||||
var showSeconds = settings.ShowSeconds;
|
||||
var format = use24Hour
|
||||
? showSeconds ? "HH:mm:ss" : "HH:mm"
|
||||
: showSeconds ? "h:mm:ss tt" : "h:mm tt";
|
||||
return time.ToString(format, culture);
|
||||
}
|
||||
|
||||
public static string FormatDuration(TimeSpan duration, bool includeMilliseconds = false)
|
||||
{
|
||||
if (duration < TimeSpan.Zero)
|
||||
{
|
||||
duration = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
var totalHours = (int)duration.TotalHours;
|
||||
return includeMilliseconds
|
||||
? string.Create(CultureInfo.InvariantCulture, $"{totalHours:D2}:{duration.Minutes:D2}:{duration.Seconds:D2}.{duration.Milliseconds / 10:D2}")
|
||||
: string.Create(CultureInfo.InvariantCulture, $"{totalHours:D2}:{duration.Minutes:D2}:{duration.Seconds:D2}");
|
||||
}
|
||||
|
||||
public static string FormatUtcOffset(TimeSpan offset)
|
||||
{
|
||||
var sign = offset >= TimeSpan.Zero ? "+" : "-";
|
||||
var totalMinutes = Math.Abs((int)Math.Round(offset.TotalMinutes));
|
||||
var hours = totalMinutes / 60;
|
||||
var minutes = totalMinutes % 60;
|
||||
return $"UTC{sign}{hours:D2}:{minutes:D2}";
|
||||
}
|
||||
|
||||
public static string ResolveCityName(TimeZoneInfo timeZone, string languageCode)
|
||||
{
|
||||
var normalizedLanguage = NormalizeLanguage(languageCode);
|
||||
if (CityNames.TryGetValue(normalizedLanguage, out var cityNames) &&
|
||||
cityNames.TryGetValue(timeZone.Id, out var cityName))
|
||||
{
|
||||
return cityName;
|
||||
}
|
||||
|
||||
var normalized = timeZone.Id;
|
||||
var slashIndex = normalized.LastIndexOf('/');
|
||||
if (slashIndex >= 0 && slashIndex < normalized.Length - 1)
|
||||
{
|
||||
normalized = normalized[(slashIndex + 1)..];
|
||||
}
|
||||
|
||||
normalized = normalized.Replace('_', ' ').Trim();
|
||||
normalized = normalized
|
||||
.Replace("Standard Time", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Daylight Time", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Time", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Trim();
|
||||
|
||||
return string.IsNullOrWhiteSpace(normalized) ? timeZone.Id : normalized;
|
||||
}
|
||||
|
||||
public static bool UseTwentyFourHourClock(string? timeFormatMode, CultureInfo culture)
|
||||
{
|
||||
return ClockAirAppTimeFormatMode.Normalize(timeFormatMode) switch
|
||||
{
|
||||
ClockAirAppTimeFormatMode.TwentyFourHour => true,
|
||||
ClockAirAppTimeFormatMode.TwelveHour => false,
|
||||
_ => !culture.DateTimeFormat.ShortTimePattern.Contains('h')
|
||||
};
|
||||
}
|
||||
|
||||
private static string NormalizeLanguage(string? languageCode)
|
||||
{
|
||||
return languageCode?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"en" or "en-us" => "en-US",
|
||||
"ja" or "ja-jp" => "ja-JP",
|
||||
"ko" or "ko-kr" => "ko-KR",
|
||||
_ => "zh-CN"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
|
||||
namespace LanMountainDesktop.Services.ClockAirApp;
|
||||
|
||||
public sealed class ClockAirAppTimerState
|
||||
{
|
||||
private TimeSpan _duration = TimeSpan.FromMinutes(5);
|
||||
private TimeSpan _remainingBeforeRun = TimeSpan.FromMinutes(5);
|
||||
private DateTimeOffset? _startedAt;
|
||||
|
||||
public TimeSpan Duration => _duration;
|
||||
|
||||
public bool IsRunning => _startedAt.HasValue;
|
||||
|
||||
public bool IsCompleted { get; private set; }
|
||||
|
||||
public TimeSpan GetRemaining(DateTimeOffset now)
|
||||
{
|
||||
if (!_startedAt.HasValue)
|
||||
{
|
||||
return _remainingBeforeRun < TimeSpan.Zero ? TimeSpan.Zero : _remainingBeforeRun;
|
||||
}
|
||||
|
||||
var remaining = _remainingBeforeRun - (now - _startedAt.Value);
|
||||
return remaining < TimeSpan.Zero ? TimeSpan.Zero : remaining;
|
||||
}
|
||||
|
||||
public void SetDuration(TimeSpan duration)
|
||||
{
|
||||
if (duration <= TimeSpan.Zero)
|
||||
{
|
||||
duration = TimeSpan.FromMinutes(1);
|
||||
}
|
||||
|
||||
_duration = duration;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void StartOrResume(DateTimeOffset now)
|
||||
{
|
||||
if (_startedAt.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_remainingBeforeRun <= TimeSpan.Zero || IsCompleted)
|
||||
{
|
||||
_remainingBeforeRun = _duration;
|
||||
IsCompleted = false;
|
||||
}
|
||||
|
||||
_startedAt = now;
|
||||
}
|
||||
|
||||
public void Pause(DateTimeOffset now)
|
||||
{
|
||||
if (!_startedAt.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_remainingBeforeRun = GetRemaining(now);
|
||||
_startedAt = null;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_remainingBeforeRun = _duration;
|
||||
_startedAt = null;
|
||||
IsCompleted = false;
|
||||
}
|
||||
|
||||
public bool Update(DateTimeOffset now)
|
||||
{
|
||||
if (!_startedAt.HasValue || GetRemaining(now) > TimeSpan.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_remainingBeforeRun = TimeSpan.Zero;
|
||||
_startedAt = null;
|
||||
if (IsCompleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IsCompleted = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user