Skip to content

Commit e0a11dd

Browse files
authored
feat: add mock UI for file syncing listing (#60)
1 parent 13ce6b9 commit e0a11dd

21 files changed

+1337
-74
lines changed

App/App.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
6666
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.1" />
6767
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
68+
<PackageReference Include="WinUIEx" Version="2.5.1" />
6869
</ItemGroup>
6970

7071
<ItemGroup>

App/App.xaml

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
<Application
44
x:Class="Coder.Desktop.App.App"
55
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
6-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
6+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
7+
xmlns:converters="using:Coder.Desktop.App.Converters">
78
<Application.Resources>
89
<ResourceDictionary>
910
<ResourceDictionary.MergedDictionaries>
1011
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
1112
</ResourceDictionary.MergedDictionaries>
13+
14+
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />
15+
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
16+
<converters:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisibilityConverter" />
17+
<converters:FriendlyByteConverter x:Key="FriendlyByteConverter" />
1218
</ResourceDictionary>
1319
</Application.Resources>
1420
</Application>

App/App.xaml.cs

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public App()
4747
services.AddTransient<SignInViewModel>();
4848
services.AddTransient<SignInWindow>();
4949

50+
// FileSyncListWindow views and view models
51+
services.AddTransient<FileSyncListViewModel>();
52+
// FileSyncListMainPage is created by FileSyncListWindow.
53+
services.AddTransient<FileSyncListWindow>();
54+
5055
// TrayWindow views and view models
5156
services.AddTransient<TrayWindowLoadingPage>();
5257
services.AddTransient<TrayWindowDisconnectedViewModel>();

App/Controls/SizedFrame.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ public class SizedFrameEventArgs : EventArgs
1212

1313
/// <summary>
1414
/// SizedFrame extends Frame by adding a SizeChanged event, which will be triggered when:
15-
/// - The contained Page's content's size changes
16-
/// - We switch to a different page.
17-
///
15+
/// - The contained Page's content's size changes
16+
/// - We switch to a different page.
1817
/// Sadly this is necessary because Window.Content.SizeChanged doesn't trigger when the Page's content changes.
1918
/// </summary>
2019
public class SizedFrame : Frame

App/Converters/AgentStatusToColorConverter.cs

-33
This file was deleted.
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
using System;
2+
using System.Linq;
3+
using Windows.Foundation.Collections;
4+
using Windows.UI.Xaml.Markup;
5+
using Microsoft.UI.Xaml;
6+
using Microsoft.UI.Xaml.Data;
7+
using Microsoft.UI.Xaml.Media;
8+
9+
namespace Coder.Desktop.App.Converters;
10+
11+
// This file uses manual DependencyProperty properties rather than
12+
// DependencyPropertyGenerator since it doesn't seem to work properly with
13+
// generics.
14+
15+
/// <summary>
16+
/// An item in a DependencyObjectSelector. Each item has a key and a value.
17+
/// The default item in a DependencyObjectSelector will be the only item
18+
/// with a null key.
19+
/// </summary>
20+
/// <typeparam name="TK">Key type</typeparam>
21+
/// <typeparam name="TV">Value type</typeparam>
22+
public class DependencyObjectSelectorItem<TK, TV> : DependencyObject
23+
where TK : IEquatable<TK>
24+
{
25+
public static readonly DependencyProperty KeyProperty =
26+
DependencyProperty.Register(nameof(Key),
27+
typeof(TK?),
28+
typeof(DependencyObjectSelectorItem<TK, TV>),
29+
new PropertyMetadata(null));
30+
31+
public static readonly DependencyProperty ValueProperty =
32+
DependencyProperty.Register(nameof(Value),
33+
typeof(TV?),
34+
typeof(DependencyObjectSelectorItem<TK, TV>),
35+
new PropertyMetadata(null));
36+
37+
public TK? Key
38+
{
39+
get => (TK?)GetValue(KeyProperty);
40+
set => SetValue(KeyProperty, value);
41+
}
42+
43+
public TV? Value
44+
{
45+
get => (TV?)GetValue(ValueProperty);
46+
set => SetValue(ValueProperty, value);
47+
}
48+
}
49+
50+
/// <summary>
51+
/// Allows selecting between multiple value references based on a selected
52+
/// key. This allows for dynamic mapping of model values to other objects.
53+
/// The main use case is for selecting between other bound values, which
54+
/// you cannot do with a simple ValueConverter.
55+
/// </summary>
56+
/// <typeparam name="TK">Key type</typeparam>
57+
/// <typeparam name="TV">Value type</typeparam>
58+
[ContentProperty(Name = nameof(References))]
59+
public class DependencyObjectSelector<TK, TV> : DependencyObject
60+
where TK : IEquatable<TK>
61+
{
62+
public static readonly DependencyProperty ReferencesProperty =
63+
DependencyProperty.Register(nameof(References),
64+
typeof(DependencyObjectCollection),
65+
typeof(DependencyObjectSelector<TK, TV>),
66+
new PropertyMetadata(null, ReferencesPropertyChanged));
67+
68+
public static readonly DependencyProperty SelectedKeyProperty =
69+
DependencyProperty.Register(nameof(SelectedKey),
70+
typeof(TK?),
71+
typeof(DependencyObjectSelector<TK, TV>),
72+
new PropertyMetadata(null, SelectedKeyPropertyChanged));
73+
74+
public static readonly DependencyProperty SelectedObjectProperty =
75+
DependencyProperty.Register(nameof(SelectedObject),
76+
typeof(TV?),
77+
typeof(DependencyObjectSelector<TK, TV>),
78+
new PropertyMetadata(null));
79+
80+
public DependencyObjectCollection? References
81+
{
82+
get => (DependencyObjectCollection?)GetValue(ReferencesProperty);
83+
set
84+
{
85+
// Ensure unique keys and that the values are DependencyObjectSelectorItem<K, V>.
86+
if (value != null)
87+
{
88+
var items = value.OfType<DependencyObjectSelectorItem<TK, TV>>().ToArray();
89+
var keys = items.Select(i => i.Key).Distinct().ToArray();
90+
if (keys.Length != value.Count)
91+
throw new ArgumentException("ObservableCollection Keys must be unique.");
92+
}
93+
94+
SetValue(ReferencesProperty, value);
95+
}
96+
}
97+
98+
/// <summary>
99+
/// The key of the selected item. This should be bound to a property on
100+
/// the model.
101+
/// </summary>
102+
public TK? SelectedKey
103+
{
104+
get => (TK?)GetValue(SelectedKeyProperty);
105+
set => SetValue(SelectedKeyProperty, value);
106+
}
107+
108+
/// <summary>
109+
/// The selected object. This can be read from to get the matching
110+
/// object for the selected key. If the selected key doesn't match any
111+
/// object, this will be the value of the null key. If there is no null
112+
/// key, this will be null.
113+
/// </summary>
114+
public TV? SelectedObject
115+
{
116+
get => (TV?)GetValue(SelectedObjectProperty);
117+
set => SetValue(SelectedObjectProperty, value);
118+
}
119+
120+
public DependencyObjectSelector()
121+
{
122+
References = [];
123+
}
124+
125+
private void UpdateSelectedObject()
126+
{
127+
if (References != null)
128+
{
129+
// Look for a matching item a matching key, or fallback to the null
130+
// key.
131+
var references = References.OfType<DependencyObjectSelectorItem<TK, TV>>().ToArray();
132+
var item = references
133+
.FirstOrDefault(i =>
134+
(i.Key == null && SelectedKey == null) ||
135+
(i.Key != null && SelectedKey != null && i.Key!.Equals(SelectedKey!)))
136+
?? references.FirstOrDefault(i => i.Key == null);
137+
if (item is not null)
138+
{
139+
// Bind the SelectedObject property to the reference's Value.
140+
// If the underlying Value changes, it will propagate to the
141+
// SelectedObject.
142+
BindingOperations.SetBinding
143+
(
144+
this,
145+
SelectedObjectProperty,
146+
new Binding
147+
{
148+
Source = item,
149+
Path = new PropertyPath(nameof(DependencyObjectSelectorItem<TK, TV>.Value)),
150+
}
151+
);
152+
return;
153+
}
154+
}
155+
156+
ClearValue(SelectedObjectProperty);
157+
}
158+
159+
// Called when the References property is replaced.
160+
private static void ReferencesPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
161+
{
162+
var self = obj as DependencyObjectSelector<TK, TV>;
163+
if (self == null) return;
164+
var oldValue = args.OldValue as DependencyObjectCollection;
165+
if (oldValue != null)
166+
oldValue.VectorChanged -= self.OnVectorChangedReferences;
167+
var newValue = args.NewValue as DependencyObjectCollection;
168+
if (newValue != null)
169+
newValue.VectorChanged += self.OnVectorChangedReferences;
170+
}
171+
172+
// Called when the References collection changes without being replaced.
173+
private void OnVectorChangedReferences(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
174+
{
175+
UpdateSelectedObject();
176+
}
177+
178+
// Called when SelectedKey changes.
179+
private static void SelectedKeyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
180+
{
181+
var self = obj as DependencyObjectSelector<TK, TV>;
182+
self?.UpdateSelectedObject();
183+
}
184+
}
185+
186+
public sealed class StringToBrushSelectorItem : DependencyObjectSelectorItem<string, Brush>;
187+
188+
public sealed class StringToBrushSelector : DependencyObjectSelector<string, Brush>;
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Microsoft.UI.Xaml.Data;
3+
4+
namespace Coder.Desktop.App.Converters;
5+
6+
public class FriendlyByteConverter : IValueConverter
7+
{
8+
private static readonly string[] Suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
9+
10+
public object Convert(object value, Type targetType, object parameter, string language)
11+
{
12+
switch (value)
13+
{
14+
case int i:
15+
if (i < 0) i = 0;
16+
return FriendlyBytes((ulong)i);
17+
case uint ui:
18+
return FriendlyBytes(ui);
19+
case long l:
20+
if (l < 0) l = 0;
21+
return FriendlyBytes((ulong)l);
22+
case ulong ul:
23+
return FriendlyBytes(ul);
24+
default:
25+
return FriendlyBytes(0);
26+
}
27+
}
28+
29+
public object ConvertBack(object value, Type targetType, object parameter, string language)
30+
{
31+
throw new NotImplementedException();
32+
}
33+
34+
public static string FriendlyBytes(ulong bytes)
35+
{
36+
if (bytes == 0)
37+
return $"0 {Suffixes[0]}";
38+
39+
var place = System.Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
40+
var num = Math.Round(bytes / Math.Pow(1024, place), 1);
41+
return $"{num} {Suffixes[place]}";
42+
}
43+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using Microsoft.UI.Xaml.Data;
3+
4+
namespace Coder.Desktop.App.Converters;
5+
6+
public class InverseBoolConverter : IValueConverter
7+
{
8+
public object Convert(object value, Type targetType, object parameter, string language)
9+
{
10+
return value is false;
11+
}
12+
13+
public object ConvertBack(object value, Type targetType, object parameter, string language)
14+
{
15+
throw new NotImplementedException();
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.UI.Xaml;
2+
3+
namespace Coder.Desktop.App.Converters;
4+
5+
public partial class InverseBoolToVisibilityConverter : BoolToObjectConverter
6+
{
7+
public InverseBoolToVisibilityConverter()
8+
{
9+
TrueValue = Visibility.Collapsed;
10+
FalseValue = Visibility.Visible;
11+
}
12+
}

0 commit comments

Comments
 (0)