Initial commit

This commit is contained in:
Stone_Red
2026-02-27 00:27:08 +01:00
commit c783e9fb9c
38 changed files with 1248 additions and 0 deletions
+28
View File
@@ -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<MessageViewModel>(
model.Messages.Select(message => new MessageViewModel(message)));
}
public ChannelModel Model { get; }
public string Name => Model.Name;
public ObservableCollection<MessageViewModel> Messages { get; }
public void AddMessage(MessageModel message)
{
Model.Messages.Add(message);
Messages.Add(new MessageViewModel(message));
}
}
+41
View File
@@ -0,0 +1,41 @@
using System.Collections.ObjectModel;
namespace Decho.ViewModels;
public sealed class ChatViewModel : ViewModelBase
{
private ObservableCollection<MessageViewModel> _messages = new();
private string _channelTitle = "Select a channel";
public ChatViewModel()
{
Composer = new MessageComposerViewModel();
}
public ObservableCollection<MessageViewModel> 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<MessageViewModel>();
ChannelTitle = "Select a channel";
return;
}
Messages = channel.Messages;
ChannelTitle = "#" + channel.Name;
}
}
+92
View File
@@ -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<ServerModel> SeedServers()
{
var alex = new UserModel("user-2", "Alex");
var sam = new UserModel("user-3", "Sam");
var general = new ChannelModel(
"channel-1",
"general",
new ObservableCollection<MessageModel>
{
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<MessageModel>
{
new("message-4", sam, DateTimeOffset.Now.AddMinutes(-5), "Random chat keeps the vibe light."),
});
var music = new ChannelModel(
"channel-3",
"music",
new ObservableCollection<MessageModel>());
var server = new ServerModel(
"server-1",
"Server",
new ObservableCollection<ChannelModel> { general, random });
var anotherServer = new ServerModel(
"server-2",
"Another Server",
new ObservableCollection<ChannelModel>() { general, music});
return new[] { server, anotherServer };
}
}
@@ -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<Unit, Unit> SendCommand { get; }
public event Action<string>? SendRequested;
private void Send()
{
var text = Draft.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
Draft = string.Empty;
SendRequested?.Invoke(text);
}
}
+19
View File
@@ -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");
}
+37
View File
@@ -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<ChannelViewModel>(
model.Channels.Select(channel => new ChannelViewModel(channel)));
}
public ServerModel Model { get; }
public string Name => Model.Name;
public ObservableCollection<ChannelViewModel> Channels { get; }
public ChannelViewModel? SelectedChannel
{
get => _selectedChannel;
set => this.RaiseAndSetIfChanged(ref _selectedChannel, value);
}
public bool IsExpanded
{
get => _isExpanded;
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
}
}
+59
View File
@@ -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<ServerModel> servers)
{
Servers = new ObservableCollection<ServerViewModel>(
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<ChannelViewModel?>(channel => SelectedChannel = channel!));
}
}
public ObservableCollection<ServerViewModel> 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;
}
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
using ReactiveUI;
namespace Decho.ViewModels;
public abstract class ViewModelBase : ReactiveObject
{
}