Skip to content

Commit 45e506f

Browse files
committed
added new settings dialog + settings manager
1 parent 22c9bcd commit 45e506f

File tree

10 files changed

+349
-3
lines changed

10 files changed

+349
-3
lines changed

App/App.xaml.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ public App()
9090
// FileSyncListMainPage is created by FileSyncListWindow.
9191
services.AddTransient<FileSyncListWindow>();
9292

93+
services.AddSingleton<ISettingsManager>(_ => new SettingsManager("CoderDesktop"));
94+
// SettingsWindow views and view models
95+
services.AddTransient<SettingsViewModel>();
96+
// SettingsMainPage is created by SettingsWindow.
97+
services.AddTransient<SettingsWindow>();
98+
9399
// DirectoryPickerWindow views and view models are created by FileSyncListViewModel.
94100

95101
// TrayWindow views and view models

App/Services/SettingsManager.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Text.Json;
7+
using System.Threading.Tasks;
8+
9+
namespace Coder.Desktop.App.Services;
10+
/// <summary>
11+
/// Generic persistence contract for simple key/value settings.
12+
/// </summary>
13+
public interface ISettingsManager
14+
{
15+
/// <summary>
16+
/// Saves <paramref name="value"/> under <paramref name="name"/> and returns the value.
17+
/// </summary>
18+
T Save<T>(string name, T value);
19+
20+
/// <summary>
21+
/// Reads the setting or returns <paramref name="defaultValue"/> when the key is missing.
22+
/// </summary>
23+
T Read<T>(string name, T defaultValue);
24+
}
25+
/// <summary>
26+
/// JSON‑file implementation that works in unpackaged Win32/WinUI 3 apps.
27+
/// </summary>
28+
public sealed class SettingsManager : ISettingsManager
29+
{
30+
private readonly string _settingsFilePath;
31+
private readonly string _fileName = "app-settings.json";
32+
private readonly object _lock = new();
33+
private Dictionary<string, JsonElement> _cache;
34+
35+
/// <param name="appName">
36+
/// Sub‑folder under %LOCALAPPDATA% (e.g. "coder-desktop").
37+
/// If <c>null</c> the folder name defaults to the executable name.
38+
/// For unit‑tests you can pass an absolute path that already exists.
39+
/// </param>
40+
public SettingsManager(string? appName = null)
41+
{
42+
// Allow unit‑tests to inject a fully‑qualified path.
43+
if (appName is not null && Path.IsPathRooted(appName))
44+
{
45+
_settingsFilePath = Path.Combine(appName, _fileName);
46+
Directory.CreateDirectory(Path.GetDirectoryName(_settingsFilePath)!);
47+
}
48+
else
49+
{
50+
string folder = Path.Combine(
51+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
52+
appName ?? AppDomain.CurrentDomain.FriendlyName.ToLowerInvariant());
53+
Directory.CreateDirectory(folder);
54+
_settingsFilePath = Path.Combine(folder, _fileName);
55+
}
56+
57+
_cache = Load();
58+
}
59+
60+
public T Save<T>(string name, T value)
61+
{
62+
lock (_lock)
63+
{
64+
_cache[name] = JsonSerializer.SerializeToElement(value);
65+
Persist();
66+
return value;
67+
}
68+
}
69+
70+
public T Read<T>(string name, T defaultValue)
71+
{
72+
lock (_lock)
73+
{
74+
if (_cache.TryGetValue(name, out var element))
75+
{
76+
try
77+
{
78+
return element.Deserialize<T>() ?? defaultValue;
79+
}
80+
catch
81+
{
82+
// Malformed value – fall back.
83+
return defaultValue;
84+
}
85+
}
86+
return defaultValue; // key not found – return caller‑supplied default (false etc.)
87+
}
88+
}
89+
90+
private Dictionary<string, JsonElement> Load()
91+
{
92+
if (!File.Exists(_settingsFilePath))
93+
return new();
94+
95+
try
96+
{
97+
using var fs = File.OpenRead(_settingsFilePath);
98+
return JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(fs) ?? new();
99+
}
100+
catch
101+
{
102+
// Corrupted file – start fresh.
103+
return new();
104+
}
105+
}
106+
107+
private void Persist()
108+
{
109+
using var fs = File.Create(_settingsFilePath);
110+
var options = new JsonSerializerOptions { WriteIndented = true };
111+
JsonSerializer.Serialize(fs, _cache, options);
112+
}
113+
}

App/ViewModels/SettingsViewModel.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using Coder.Desktop.App.Services;
3+
using CommunityToolkit.Mvvm.ComponentModel;
4+
using CommunityToolkit.Mvvm.Input;
5+
using Microsoft.UI.Dispatching;
6+
using Microsoft.UI.Xaml;
7+
using Microsoft.UI.Xaml.Controls;
8+
9+
namespace Coder.Desktop.App.ViewModels;
10+
11+
public partial class SettingsViewModel : ObservableObject
12+
{
13+
private Window? _window;
14+
private DispatcherQueue? _dispatcherQueue;
15+
16+
private ISettingsManager _settingsManager;
17+
18+
public SettingsViewModel(ISettingsManager settingsManager)
19+
{
20+
_settingsManager = settingsManager;
21+
}
22+
23+
public void Initialize(Window window, DispatcherQueue dispatcherQueue)
24+
{
25+
_window = window;
26+
_dispatcherQueue = dispatcherQueue;
27+
if (!_dispatcherQueue.HasThreadAccess)
28+
throw new InvalidOperationException("Initialize must be called from the UI thread");
29+
}
30+
31+
[RelayCommand]
32+
private void SaveSetting()
33+
{
34+
//_settingsManager.Save();
35+
}
36+
37+
[RelayCommand]
38+
private void ShowSettingsDialog()
39+
{
40+
if (_window is null || _dispatcherQueue is null)
41+
throw new InvalidOperationException("Initialize must be called before showing the settings dialog.");
42+
// Here you would typically open a settings dialog or page.
43+
// For example, you could navigate to a SettingsPage in your app.
44+
// This is just a placeholder for demonstration purposes.
45+
// Display MessageBox and show a message
46+
var message = $"Settings dialog opened. Current setting: {_settingsManager.Read("SomeSetting", false)}\n" +
47+
"You can implement your settings dialog here.";
48+
var dialog = new ContentDialog();
49+
dialog.Title = "Settings";
50+
dialog.Content = message;
51+
dialog.XamlRoot = _window.Content.XamlRoot;
52+
_ = dialog.ShowAsync();
53+
}
54+
}

App/ViewModels/TrayWindowViewModel.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
3939

4040
private FileSyncListWindow? _fileSyncListWindow;
4141

42+
private SettingsWindow? _settingsWindow;
43+
4244
private DispatcherQueue? _dispatcherQueue;
4345

4446
// When we transition from 0 online workspaces to >0 online workspaces, the
@@ -359,6 +361,22 @@ private void ShowFileSyncListWindow()
359361
_fileSyncListWindow.Activate();
360362
}
361363

364+
[RelayCommand]
365+
private void ShowSettingsWindow()
366+
{
367+
// This is safe against concurrent access since it all happens in the
368+
// UI thread.
369+
if (_settingsWindow != null)
370+
{
371+
_settingsWindow.Activate();
372+
return;
373+
}
374+
375+
_settingsWindow = _services.GetRequiredService<SettingsWindow>();
376+
_settingsWindow.Closed += (_, _) => _settingsWindow = null;
377+
_settingsWindow.Activate();
378+
}
379+
362380
[RelayCommand]
363381
private async Task SignOut()
364382
{

App/Views/Pages/SettingsMainPage.xaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<Page
4+
x:Class="Coder.Desktop.App.Views.Pages.SettingsMainPage"
5+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
6+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9+
xmlns:viewmodels="using:Coder.Desktop.App.ViewModels"
10+
xmlns:converters="using:Coder.Desktop.App.Converters"
11+
mc:Ignorable="d"
12+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
13+
14+
<Grid>
15+
<HyperlinkButton
16+
Command="{x:Bind ViewModel.ShowSettingsDialogCommand, Mode=OneWay}"
17+
HorizontalAlignment="Stretch"
18+
HorizontalContentAlignment="Left">
19+
20+
<TextBlock Text="Show settings" Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" />
21+
</HyperlinkButton>
22+
</Grid>
23+
</Page>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Coder.Desktop.App.ViewModels;
2+
using Microsoft.UI.Xaml.Controls;
3+
4+
namespace Coder.Desktop.App.Views.Pages;
5+
6+
public sealed partial class SettingsMainPage : Page
7+
{
8+
public SettingsViewModel ViewModel;
9+
10+
public SettingsMainPage(SettingsViewModel viewModel)
11+
{
12+
ViewModel = viewModel; // already initialized
13+
InitializeComponent();
14+
}
15+
}

App/Views/Pages/TrayWindowMainPage.xaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
Orientation="Vertical"
2626
HorizontalAlignment="Stretch"
2727
VerticalAlignment="Top"
28-
Padding="20,20,20,30"
28+
Padding="20,20,20,20"
2929
Spacing="10">
3030

3131
<Grid>
@@ -331,9 +331,18 @@
331331

332332
<controls:HorizontalRule />
333333

334+
<HyperlinkButton
335+
Command="{x:Bind ViewModel.ShowSettingsWindowCommand, Mode=OneWay}"
336+
Margin="-12,-4,-12,-4"
337+
HorizontalAlignment="Stretch"
338+
HorizontalContentAlignment="Left">
339+
340+
<TextBlock Text="Settings" Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" />
341+
</HyperlinkButton>
342+
334343
<HyperlinkButton
335344
Command="{x:Bind ViewModel.SignOutCommand, Mode=OneWay}"
336-
Margin="-12,0"
345+
Margin="-12,-4,-12,-4"
337346
HorizontalAlignment="Stretch"
338347
HorizontalContentAlignment="Left">
339348

@@ -342,7 +351,7 @@
342351

343352
<HyperlinkButton
344353
Command="{x:Bind ViewModel.ExitCommand, Mode=OneWay}"
345-
Margin="-12,-8,-12,-5"
354+
Margin="-12,-4,-12,-4"
346355
HorizontalAlignment="Stretch"
347356
HorizontalContentAlignment="Left">
348357

App/Views/SettingsWindow.xaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<winuiex:WindowEx
4+
x:Class="Coder.Desktop.App.Views.SettingsWindow"
5+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
6+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
7+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
9+
xmlns:winuiex="using:WinUIEx"
10+
mc:Ignorable="d"
11+
Title="Coder Settings"
12+
Width="1000" Height="300"
13+
MinWidth="1000" MinHeight="300">
14+
15+
<Window.SystemBackdrop>
16+
<DesktopAcrylicBackdrop />
17+
</Window.SystemBackdrop>
18+
19+
<Frame x:Name="RootFrame" />
20+
</winuiex:WindowEx>

App/Views/SettingsWindow.xaml.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using Coder.Desktop.App.Utils;
2+
using Coder.Desktop.App.ViewModels;
3+
using Coder.Desktop.App.Views.Pages;
4+
using Microsoft.UI.Xaml.Media;
5+
using WinUIEx;
6+
7+
namespace Coder.Desktop.App.Views;
8+
9+
public sealed partial class SettingsWindow : WindowEx
10+
{
11+
public readonly SettingsViewModel ViewModel;
12+
13+
public SettingsWindow(SettingsViewModel viewModel)
14+
{
15+
ViewModel = viewModel;
16+
InitializeComponent();
17+
TitleBarIcon.SetTitlebarIcon(this);
18+
19+
SystemBackdrop = new DesktopAcrylicBackdrop();
20+
21+
ViewModel.Initialize(this, DispatcherQueue);
22+
RootFrame.Content = new SettingsMainPage(ViewModel);
23+
24+
this.CenterOnScreen();
25+
}
26+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy