using System.ComponentModel; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using System.Windows.Media; using System.Windows.Threading; using CursorLang.Models; namespace CursorLang.Services; /// /// Хранит настройки в %APPDATA%\CursorLang\settings.json. /// public sealed class SettingsService : IDisposable { private static readonly JsonSerializerOptions SerializerOptions = new() { WriteIndented = true, Converters = { new ColorJsonConverter(), new JsonStringEnumConverter() }, }; private readonly string _filePath; private readonly DispatcherTimer _saveTimer; private AppSettings? _settings; public SettingsService() { string folder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CursorLang"); _filePath = Path.Combine(folder, "settings.json"); // Ползунки меняют значения непрерывно, поэтому запись на диск // откладывается до паузы в изменениях _saveTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; _saveTimer.Tick += OnSaveTimerTick; } /// /// Читает настройки с диска либо отдаёт значения по умолчанию, /// и дальше сам сохраняет любые изменения. /// public AppSettings Load() { _settings = ReadFile() ?? CreateDefault(); _settings.PropertyChanged += OnSettingsChanged; return _settings; } public void Save() { if (_settings is null) { return; } try { Directory.CreateDirectory(Path.GetDirectoryName(_filePath)!); File.WriteAllText(_filePath, JsonSerializer.Serialize(_settings, SerializerOptions)); } catch (Exception e) when (e is IOException or UnauthorizedAccessException) { // Настройки — не тот случай, ради которого стоит ронять приложение } } public void Dispose() { _saveTimer.Stop(); _saveTimer.Tick -= OnSaveTimerTick; if (_settings is not null) { _settings.PropertyChanged -= OnSettingsChanged; Save(); } } private AppSettings? ReadFile() { try { if (!File.Exists(_filePath)) { return null; } return JsonSerializer.Deserialize(File.ReadAllText(_filePath), SerializerOptions); } catch (Exception e) when (e is IOException or UnauthorizedAccessException or JsonException) { return null; } } private static AppSettings CreateDefault() { string uiLanguage = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; return new AppSettings { Language = uiLanguage == "ru" ? "ru" : "en" }; } private void OnSettingsChanged(object? sender, PropertyChangedEventArgs e) { _saveTimer.Stop(); _saveTimer.Start(); } private void OnSaveTimerTick(object? sender, EventArgs e) { _saveTimer.Stop(); Save(); } // Color не сериализуется штатно, а хранить его читаемым в файле удобно private sealed class ColorJsonConverter : JsonConverter { public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string? value = reader.GetString(); if (string.IsNullOrWhiteSpace(value)) { return Colors.Black; } try { return (Color)ColorConverter.ConvertFromString(value)!; } catch (FormatException) { return Colors.Black; } } public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); } } }