)8r&*L3PA
zGNYd+_^;nNKmTI~dY_~zXvYtC&`V1XP`{HN)&XeWGrj5UlH
zZee!1VNvH+5`n^X)VT_uHU@y|7KTyy(Z?5qDg$KFEcW)=_8v;X0rL`a)!hGn@n}0a
z!pymqFnJDNAMzAKwQ-_dH6PqoTW*@0bIGbgg_4V*9x0{sQ*CWmB{
zCeF05wqYfu%aUy;K=dliZ4bLVR6F%cgcF4tU#>fXa5Jho-GFog`9%anGa*?tt#`gv51zbJFbZpG+=eKbo&brl>piU(=FUrL*v%^1KT3SwBH<`1;$<}htc5GWLgY8n5X8dRx6S|dRbF$(&MAHwb{23GZb^tG;PV4|Q)OA)CH&$x5x
z^6A@po{orr<=f}~?N`B}df>Ti>g_7WArV5DI`Trc&QEbgq*`4VnY)=@(#yaHX^V!r
zbo+-j2rVb$%#S2pGbKPbZ)8D`hqBPEdT+)>YFSB!-rhmAHjS!yyDInZtEXnXt^9c&
zU)dIgeaii;y+7{#f=8PD`~G#$CK0;|*4f0gy~S5`iK?oCG{r%CriEw#NZIGH=7QR8
zwmXwIhA$ili;#J>xLoOKaP)Xx0ZSNmFV68J@XqI6eE>l8x?q>`Q1BaA`9;IOAqO)Y
z`-g#*1AQlkop833=9a0X_WK<)qZ$4u4Kb|@w!~L`Kl_u!og!QcNMFlRtpK%V#zEKd
z9`F?8_RCfii3E^sbSIuY$PbLg3^0qTd6?^*Bz<#w{WGw(NzIxBoM71GHc$F_CCDK|
zD#O*CdpRM40kPB2W;pi&D?_|p-{Y}rB&9furJCJ_BzE`$JErD%1mj7dvytXv}NCML!)g&Fjnqns32!WA~{ZIEY3HLstQ&@55lm4W9r
zQF(g9g`z+N>UVvVsPLmnVA?8(2_dO
z@L(I;NA&Ec9)Nbkw?FG>EBUA2@XihhLMM6rL>=%s8qRV|uI%V6lvM?QfYjKZNS|Ne
z@78l?1G?&t4{^)ENK2xK9-^CxwB?9mAkdc%%1EHnd8G1N0Rx7HNbkuA0ba0V=rQ|
zVt>}Ta!mPuQ*+dDGRjJrDviGR48=Gk`%;Ca(=GQWony#EsRSr4;h~H`q$FGGaq`mN
z0DwZyyop0a|BGgrb+rK1qj#R9I2IGM%G8Jw<__NI>4HDVRC~a2h=Fb1ySb^RuQnM?
zJ4NxA<&_KVpg$Q>{)&?0%8FaV9hs%j;5pnz`j@|
zF+1rzyYz;z)9R%&42Qrj!I$v5PVFLfoL_LiQNw592kSC){6wLA+e`Y)Kxg84j{{>^
z@Z>0DC{e%GEP30%6e1XUbqvGae@NgNFM#IANo)k9cIRnP-}2$Yx*)!`Vk9l^-8)cP
zbY|#7f5HzI1};cY`6te0UR6Bp21_DRC34xzXpi|
zd63C~WVSvzthcq09*E%CyU6+|`bK3=%OTX>jFE2Cv4prn?R7&_dm--vM@dpBJi_mq
zff)M+PtK_~`vdAUqM7?cEAYVSESgJG*9K!B(QuN(-rmuxDofgKzfwBSp#PE|i(
zB;r7|D`RL7DZAc1t6Ur|vjKR`Jfw?^hWMopf~-gQb_x5M1Bd`AN?j}mkil!@f6Cn4
zo^(kIJJT5f8>jS-@>C~RTUjq%Edd5k=8o24R>e6rvl}U*Gj1E}%(c#9YC)K2;)Trs
zq1nhdW{%?n(#N^m<~mF`kvf}pW`$g*_-vMub%IZmE_Zz`Z~LzAp|PFSF46Q0+L?eU
zR9$s;^ePI#%Eh~$YwrMF2%E8g_lj`$RA*aOdZ6y51jvXoae&
z&i=gm4N$PP190>KO=ySBI<3uU@~?j8aF6-P@(kdyNFtz0JJ
zQ@%~Rl~vh9BsPFaVx^8aBBs&?T1d$$O78P%*=>GKEscN{@TSS`R^UVF%CXENN5$-)
ztYD0$Pz_a8b&dZ5YZd5+V28!WW!rA%fHsPh1k_cXku)*^;*L@u-|&T!^q3D{4?EHe
zK`Q&3!#AC|mIlEvzH}U8#w_p{EireN{skqK?Z3u}3^X8oCBHD&YyjY6pz}z`5pH38
z`fCpsLtbJ}KJQ(l_yt&!v^NYa&BvR5M7Iuzzhwf7T7=3*5kb
zYI}2TTe$^U(|^zAgOd$Qz*lFSpXRFHRhyk~u1me9r}ovMMw}xFwJIB1r0*WvO&A`}
zlHG08^aKdiACDQy$Ay}7Wi^YD^o>GJ5t>qLbrr|i?!K1$pVTQGzy4q+1TaO$(ovhX
zR8#g&(wwt{iDdO;u5r~Cj@DMeADcP(r=~LxypIWCH|VxDptC+>)vkwnk6*{CpZt;0
z>r>E773uH3AwL-RMhguH@ss@v=!CraOe|&YSELx2p1#dk!*`?kM9}#ADJp8)Nczyk
zo(~iWwwo=&)!^Z6(ZmY0I_r3P3o@MB1R7AEOT$*{j?0&=1^AhK7%)(@!weecbiwV0
z9;_g7!s+uKnhsNoXWW$nJOOZie~j}
zpDKFj?*H11EuU9S5M7kLFt`JxBTCtip}2mW%?=k_?(|to{=WMcTyTFwcpzi
zJ-Kqr`U#W%OgLK0)o;83=hbeGs9^Tdp~>1{!aOp^N5O+YQmyR3bY7N3bVtM0PXol)wjH7(C45kZYIsHj3}AV89^$9?~Ifmv%UpAd}6EvRuc@8pyq
zOpUe-bn@WX#DNrWFv6`pYgl{5C!dEw>`l`c#OC0pctZzPs8sN^vu7>n%E@{IxCX(>
zT*4$DiDgPB(Qe$DsJlf5?x0Y@eDL83eoHvn6-pp+?GzwIK+4XMnd*D
zq~MsiUX(y%+nC=i8p3g#RJAwQUK#S>>Wn+e=r}(li)P~i!*njngB8oys<#G9yj{xx
zU`wzWHOydZ>`Y)%A0o4m1$K%_Az$=B{O~txVgyUdM^*tZz+KfeXe9L1A$VU=5U5IK_-l
zR#^ENCit=Le$l?XGlXLyF!`@f_hq)x
+
+ WinExe
+ net10.0
+ enable
+ app.manifest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
+
+
diff --git a/Decho/Models/ChannelModel.cs b/Decho/Models/ChannelModel.cs
new file mode 100644
index 0000000..dd72e12
--- /dev/null
+++ b/Decho/Models/ChannelModel.cs
@@ -0,0 +1,19 @@
+using System.Collections.ObjectModel;
+
+namespace Decho.Models;
+
+public sealed class ChannelModel
+{
+ public ChannelModel(string id, string name, ObservableCollection messages)
+ {
+ Id = id;
+ Name = name;
+ Messages = messages;
+ }
+
+ public string Id { get; }
+
+ public string Name { get; }
+
+ public ObservableCollection Messages { get; }
+}
diff --git a/Decho/Models/MessageModel.cs b/Decho/Models/MessageModel.cs
new file mode 100644
index 0000000..0822ba0
--- /dev/null
+++ b/Decho/Models/MessageModel.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Decho.Models;
+
+public sealed class MessageModel
+{
+ public MessageModel(string id, UserModel author, DateTimeOffset sentAt, string content)
+ {
+ Id = id;
+ Author = author;
+ SentAt = sentAt;
+ Content = content;
+ }
+
+ public string Id { get; }
+
+ public UserModel Author { get; }
+
+ public DateTimeOffset SentAt { get; }
+
+ public string Content { get; }
+}
diff --git a/Decho/Models/ServerModel.cs b/Decho/Models/ServerModel.cs
new file mode 100644
index 0000000..86509e2
--- /dev/null
+++ b/Decho/Models/ServerModel.cs
@@ -0,0 +1,19 @@
+using System.Collections.ObjectModel;
+
+namespace Decho.Models;
+
+public sealed class ServerModel
+{
+ public ServerModel(string id, string name, ObservableCollection channels)
+ {
+ Id = id;
+ Name = name;
+ Channels = channels;
+ }
+
+ public string Id { get; }
+
+ public string Name { get; }
+
+ public ObservableCollection Channels { get; }
+}
diff --git a/Decho/Models/UserModel.cs b/Decho/Models/UserModel.cs
new file mode 100644
index 0000000..6d8f328
--- /dev/null
+++ b/Decho/Models/UserModel.cs
@@ -0,0 +1,14 @@
+namespace Decho.Models;
+
+public sealed class UserModel
+{
+ public UserModel(string id, string displayName)
+ {
+ Id = id;
+ DisplayName = displayName;
+ }
+
+ public string Id { get; }
+
+ public string DisplayName { get; }
+}
diff --git a/Decho/Program.cs b/Decho/Program.cs
new file mode 100644
index 0000000..9b6a31a
--- /dev/null
+++ b/Decho/Program.cs
@@ -0,0 +1,34 @@
+global using ReactiveUI;
+global using ReactiveUI.Avalonia;
+
+using Avalonia;
+
+using Projektanker.Icons.Avalonia;
+using Projektanker.Icons.Avalonia.FontAwesome;
+
+using System;
+
+namespace Decho;
+
+internal sealed class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ {
+ IconProvider.Current
+ .Register();
+
+ return AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace()
+ .UseReactiveUI();
+ }
+}
diff --git a/Decho/ViewLocator.cs b/Decho/ViewLocator.cs
new file mode 100644
index 0000000..36bcb58
--- /dev/null
+++ b/Decho/ViewLocator.cs
@@ -0,0 +1,38 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+
+using Decho.ViewModels;
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Decho;
+///
+/// Given a view model, returns the corresponding view if possible.
+///
+[RequiresUnreferencedCode(
+ "Default implementation of ViewLocator involves reflection which may be trimmed away.",
+ Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
+public class ViewLocator : IDataTemplate
+{
+ public Control? Build(object? param)
+ {
+ if (param is null)
+ return null;
+
+ var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+
+ public bool Match(object? data)
+ {
+ return data is ViewModelBase;
+ }
+}
diff --git a/Decho/ViewModels/ChannelViewModel.cs b/Decho/ViewModels/ChannelViewModel.cs
new file mode 100644
index 0000000..f183dde
--- /dev/null
+++ b/Decho/ViewModels/ChannelViewModel.cs
@@ -0,0 +1,28 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+
+using Decho.Models;
+
+namespace Decho.ViewModels;
+
+public sealed class ChannelViewModel : ViewModelBase
+{
+ public ChannelViewModel(ChannelModel model)
+ {
+ Model = model;
+ Messages = new ObservableCollection(
+ model.Messages.Select(message => new MessageViewModel(message)));
+ }
+
+ public ChannelModel Model { get; }
+
+ public string Name => Model.Name;
+
+ public ObservableCollection Messages { get; }
+
+ public void AddMessage(MessageModel message)
+ {
+ Model.Messages.Add(message);
+ Messages.Add(new MessageViewModel(message));
+ }
+}
diff --git a/Decho/ViewModels/ChatViewModel.cs b/Decho/ViewModels/ChatViewModel.cs
new file mode 100644
index 0000000..74f7587
--- /dev/null
+++ b/Decho/ViewModels/ChatViewModel.cs
@@ -0,0 +1,41 @@
+using System.Collections.ObjectModel;
+
+namespace Decho.ViewModels;
+
+public sealed class ChatViewModel : ViewModelBase
+{
+ private ObservableCollection _messages = new();
+ private string _channelTitle = "Select a channel";
+
+ public ChatViewModel()
+ {
+ Composer = new MessageComposerViewModel();
+ }
+
+ public ObservableCollection Messages
+ {
+ get => _messages;
+ private set => this.RaiseAndSetIfChanged(ref _messages, value);
+ }
+
+ public string ChannelTitle
+ {
+ get => _channelTitle;
+ private set => this.RaiseAndSetIfChanged(ref _channelTitle, value);
+ }
+
+ public MessageComposerViewModel Composer { get; }
+
+ public void SetChannel(ChannelViewModel? channel)
+ {
+ if (channel is null)
+ {
+ Messages = new ObservableCollection();
+ ChannelTitle = "Select a channel";
+ return;
+ }
+
+ Messages = channel.Messages;
+ ChannelTitle = "#" + channel.Name;
+ }
+}
diff --git a/Decho/ViewModels/MainWindowViewModel.cs b/Decho/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..e1bfb37
--- /dev/null
+++ b/Decho/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Reactive.Linq;
+
+using Decho.Models;
+using ReactiveUI;
+
+namespace Decho.ViewModels;
+
+public sealed class MainWindowViewModel : ViewModelBase
+{
+ private readonly UserModel _currentUser;
+
+ public string Title { get; } = "Decho";
+
+ public SidebarViewModel Sidebar { get; }
+
+ public ChatViewModel Chat { get; }
+
+ public MainWindowViewModel()
+ {
+ _currentUser = new UserModel("user-1", "You");
+
+ var servers = SeedServers();
+ Sidebar = new SidebarViewModel(servers);
+ Chat = new ChatViewModel();
+
+ Sidebar.WhenAnyValue(x => x.SelectedChannel)
+ .Subscribe(channel => Chat.SetChannel(channel));
+
+ Chat.Composer.SendRequested += HandleSendRequested;
+
+ if (Sidebar.SelectedChannel is not null)
+ Chat.SetChannel(Sidebar.SelectedChannel);
+ }
+
+ private void HandleSendRequested(string text)
+ {
+ if (Sidebar.SelectedChannel is null)
+ return;
+
+ var message = new MessageModel(
+ Guid.NewGuid().ToString("N"),
+ _currentUser,
+ DateTimeOffset.Now,
+ text);
+
+ Sidebar.SelectedChannel.AddMessage(message);
+ }
+
+ private static IReadOnlyList SeedServers()
+ {
+ var alex = new UserModel("user-2", "Alex");
+ var sam = new UserModel("user-3", "Sam");
+
+ var general = new ChannelModel(
+ "channel-1",
+ "general",
+ new ObservableCollection
+ {
+ new("message-1", alex, DateTimeOffset.Now.AddMinutes(-30), "This is a message."),
+ new("message-2", sam, DateTimeOffset.Now.AddMinutes(-25), "Another message for the channel."),
+ new("message-3", alex, DateTimeOffset.Now.AddMinutes(-10), "We should test the new layout."),
+ });
+
+ var random = new ChannelModel(
+ "channel-2",
+ "random",
+ new ObservableCollection
+ {
+ new("message-4", sam, DateTimeOffset.Now.AddMinutes(-5), "Random chat keeps the vibe light."),
+ });
+
+ var music = new ChannelModel(
+ "channel-3",
+ "music",
+ new ObservableCollection());
+
+ var server = new ServerModel(
+ "server-1",
+ "Server",
+ new ObservableCollection { general, random });
+
+ var anotherServer = new ServerModel(
+ "server-2",
+ "Another Server",
+ new ObservableCollection() { general, music});
+
+ return new[] { server, anotherServer };
+ }
+}
diff --git a/Decho/ViewModels/MessageComposerViewModel.cs b/Decho/ViewModels/MessageComposerViewModel.cs
new file mode 100644
index 0000000..e298535
--- /dev/null
+++ b/Decho/ViewModels/MessageComposerViewModel.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Reactive;
+using System.Reactive.Linq;
+
+using ReactiveUI;
+
+namespace Decho.ViewModels;
+
+public sealed class MessageComposerViewModel : ViewModelBase
+{
+ private string _draft = string.Empty;
+
+ public MessageComposerViewModel()
+ {
+ var canSend = this.WhenAnyValue(x => x.Draft, draft => !string.IsNullOrWhiteSpace(draft));
+ SendCommand = ReactiveCommand.Create(Send, canSend);
+ }
+
+ public string Draft
+ {
+ get => _draft;
+ set => this.RaiseAndSetIfChanged(ref _draft, value);
+ }
+
+ public ReactiveCommand SendCommand { get; }
+
+ public event Action? SendRequested;
+
+ private void Send()
+ {
+ var text = Draft.Trim();
+ if (string.IsNullOrWhiteSpace(text))
+ return;
+
+ Draft = string.Empty;
+ SendRequested?.Invoke(text);
+ }
+}
diff --git a/Decho/ViewModels/MessageViewModel.cs b/Decho/ViewModels/MessageViewModel.cs
new file mode 100644
index 0000000..b878f3a
--- /dev/null
+++ b/Decho/ViewModels/MessageViewModel.cs
@@ -0,0 +1,19 @@
+using Decho.Models;
+
+namespace Decho.ViewModels;
+
+public sealed class MessageViewModel : ViewModelBase
+{
+ public MessageViewModel(MessageModel model)
+ {
+ Model = model;
+ }
+
+ public MessageModel Model { get; }
+
+ public string AuthorName => Model.Author.DisplayName;
+
+ public string Content => Model.Content;
+
+ public string TimeText => Model.SentAt.ToLocalTime().ToString("h:mm tt");
+}
diff --git a/Decho/ViewModels/ServerViewModel.cs b/Decho/ViewModels/ServerViewModel.cs
new file mode 100644
index 0000000..d0be4b7
--- /dev/null
+++ b/Decho/ViewModels/ServerViewModel.cs
@@ -0,0 +1,37 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+
+using Decho.Models;
+
+namespace Decho.ViewModels;
+
+public sealed class ServerViewModel : ViewModelBase
+{
+ private bool _isExpanded = true;
+ private ChannelViewModel? _selectedChannel;
+
+ public ServerViewModel(ServerModel model)
+ {
+ Model = model;
+ Channels = new ObservableCollection(
+ model.Channels.Select(channel => new ChannelViewModel(channel)));
+ }
+
+ public ServerModel Model { get; }
+
+ public string Name => Model.Name;
+
+ public ObservableCollection Channels { get; }
+
+ public ChannelViewModel? SelectedChannel
+ {
+ get => _selectedChannel;
+ set => this.RaiseAndSetIfChanged(ref _selectedChannel, value);
+ }
+
+ public bool IsExpanded
+ {
+ get => _isExpanded;
+ set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
+ }
+}
diff --git a/Decho/ViewModels/SidebarViewModel.cs b/Decho/ViewModels/SidebarViewModel.cs
new file mode 100644
index 0000000..a75e7a7
--- /dev/null
+++ b/Decho/ViewModels/SidebarViewModel.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive.Linq;
+
+using Decho.Models;
+using ReactiveUI;
+
+namespace Decho.ViewModels;
+
+public sealed class SidebarViewModel : ViewModelBase
+{
+ private ChannelViewModel? _selectedChannel;
+
+ public SidebarViewModel(IEnumerable servers)
+ {
+ Servers = new ObservableCollection(
+ servers.Select(server => new ServerViewModel(server)));
+ SelectedChannel = Servers.FirstOrDefault()?.Channels.FirstOrDefault();
+
+ foreach (var server in Servers)
+ {
+ server.WhenAnyValue(s => s.SelectedChannel)
+ .Where(channel => channel is not null)
+ .Subscribe(new Action(channel => SelectedChannel = channel!));
+ }
+ }
+
+ public ObservableCollection Servers { get; }
+
+ public ChannelViewModel? SelectedChannel
+ {
+ get => _selectedChannel;
+ set
+ {
+ if (ReferenceEquals(_selectedChannel, value))
+ return;
+
+ this.RaiseAndSetIfChanged(ref _selectedChannel, value);
+
+ if (value is null)
+ return;
+
+ foreach (var server in Servers)
+ {
+ if (server.Channels.Contains(value))
+ {
+ if (!ReferenceEquals(server.SelectedChannel, value))
+ server.SelectedChannel = value;
+ }
+ else if (server.SelectedChannel is not null)
+ {
+ server.SelectedChannel = null;
+ }
+ }
+ }
+ }
+}
diff --git a/Decho/ViewModels/ViewModelBase.cs b/Decho/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..0849b40
--- /dev/null
+++ b/Decho/ViewModels/ViewModelBase.cs
@@ -0,0 +1,7 @@
+using ReactiveUI;
+
+namespace Decho.ViewModels;
+
+public abstract class ViewModelBase : ReactiveObject
+{
+}
diff --git a/Decho/Views/ChannelListView.axaml b/Decho/Views/ChannelListView.axaml
new file mode 100644
index 0000000..21b7d5b
--- /dev/null
+++ b/Decho/Views/ChannelListView.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Decho/Views/ChannelListView.axaml.cs b/Decho/Views/ChannelListView.axaml.cs
new file mode 100644
index 0000000..bad7c1e
--- /dev/null
+++ b/Decho/Views/ChannelListView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Decho.Views;
+
+public partial class ChannelListView : UserControl
+{
+ public ChannelListView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Decho/Views/ChatView.axaml b/Decho/Views/ChatView.axaml
new file mode 100644
index 0000000..25a81b9
--- /dev/null
+++ b/Decho/Views/ChatView.axaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/Decho/Views/ChatView.axaml.cs b/Decho/Views/ChatView.axaml.cs
new file mode 100644
index 0000000..ade7e9a
--- /dev/null
+++ b/Decho/Views/ChatView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Decho.Views;
+
+public partial class ChatView : UserControl
+{
+ public ChatView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Decho/Views/MainWindow.axaml b/Decho/Views/MainWindow.axaml
new file mode 100644
index 0000000..6ac37e9
--- /dev/null
+++ b/Decho/Views/MainWindow.axaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Decho/Views/MainWindow.axaml.cs b/Decho/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..5eccf03
--- /dev/null
+++ b/Decho/Views/MainWindow.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Decho.Views;
+
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Decho/Views/MessageComposerView.axaml b/Decho/Views/MessageComposerView.axaml
new file mode 100644
index 0000000..a6cb502
--- /dev/null
+++ b/Decho/Views/MessageComposerView.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/Decho/Views/MessageComposerView.axaml.cs b/Decho/Views/MessageComposerView.axaml.cs
new file mode 100644
index 0000000..dc80d72
--- /dev/null
+++ b/Decho/Views/MessageComposerView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Decho.Views;
+
+public partial class MessageComposerView : UserControl
+{
+ public MessageComposerView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Decho/Views/MessageItemView.axaml b/Decho/Views/MessageItemView.axaml
new file mode 100644
index 0000000..e4c52f5
--- /dev/null
+++ b/Decho/Views/MessageItemView.axaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/Decho/Views/MessageItemView.axaml.cs b/Decho/Views/MessageItemView.axaml.cs
new file mode 100644
index 0000000..15cd9fd
--- /dev/null
+++ b/Decho/Views/MessageItemView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Decho.Views;
+
+public partial class MessageItemView : UserControl
+{
+ public MessageItemView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Decho/Views/MessageListView.axaml b/Decho/Views/MessageListView.axaml
new file mode 100644
index 0000000..23005c0
--- /dev/null
+++ b/Decho/Views/MessageListView.axaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Decho/Views/MessageListView.axaml.cs b/Decho/Views/MessageListView.axaml.cs
new file mode 100644
index 0000000..7bdac3e
--- /dev/null
+++ b/Decho/Views/MessageListView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Decho.Views;
+
+public partial class MessageListView : UserControl
+{
+ public MessageListView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Decho/Views/SidebarView.axaml b/Decho/Views/SidebarView.axaml
new file mode 100644
index 0000000..1c31903
--- /dev/null
+++ b/Decho/Views/SidebarView.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+