Switch MessageModel to use attachments list and update related UI logic

This commit is contained in:
Stone_Red
2026-07-16 19:57:23 +02:00
parent 24e1672420
commit 5a0ff2ddfe
5 changed files with 70 additions and 86 deletions
+3 -9
View File
@@ -1,8 +1,8 @@
using EchoHub.Core.Models;
using EchoHub.Core.DTOs;
namespace Decho.Models;
public sealed class MessageModel(string id, UserModel author, DateTimeOffset sentAt, string content, string channelName, string? serverUrl = null, MessageType type = MessageType.Text, string? attachmentUrl = null, string? attachmentFileName = null, long? attachmentFileSize = null)
public sealed class MessageModel(string id, UserModel author, DateTimeOffset sentAt, string content, string channelName, string? serverUrl = null, List<AttachmentDto>? attachments = null)
{
public string Id { get; } = id;
@@ -16,11 +16,5 @@ public sealed class MessageModel(string id, UserModel author, DateTimeOffset sen
public string? ServerUrl { get; } = serverUrl;
public MessageType Type { get; } = type;
public string? AttachmentUrl { get; } = attachmentUrl;
public string? AttachmentFileName { get; } = attachmentFileName;
public long? AttachmentFileSize { get; } = attachmentFileSize;
public List<AttachmentDto> Attachments { get; } = attachments ?? [];
}
+2 -23
View File
@@ -574,25 +574,7 @@ public sealed class ConnectionService : IDisposable
dto.SenderUsername,
dto.SenderNicknameColor);
MessageType type = MessageType.Text;
string? attachmentUrl = null;
string? attachmentFileName = null;
long? attachmentFileSize = null;
if (dto.Attachments is { Count: > 0 })
{
AttachmentDto first = dto.Attachments[0];
type = first.Kind switch
{
AttachmentKind.Image => MessageType.Image,
AttachmentKind.Audio => MessageType.Audio,
AttachmentKind.File => MessageType.File,
_ => MessageType.File
};
attachmentUrl = first.Url;
attachmentFileName = first.FileName;
attachmentFileSize = first.FileSize;
}
List<AttachmentDto> attachments = dto.Attachments ?? [];
return new MessageModel(
dto.Id.ToString("N"),
@@ -601,10 +583,7 @@ public sealed class ConnectionService : IDisposable
dto.Content,
dto.ChannelName,
entry.Server.ServerUrl,
type,
attachmentUrl,
attachmentFileName,
attachmentFileSize);
attachments);
}
internal ServerConnection? GetConnection(string serverUrl)
+4 -14
View File
@@ -3,7 +3,7 @@ using Avalonia.Media.Imaging;
using Decho.Models;
using EchoHub.Core.Models;
using EchoHub.Core.DTOs;
namespace Decho.ViewModels;
@@ -11,8 +11,6 @@ public sealed class MessageViewModel(MessageModel model) : ViewModelBase
{
public MessageModel Model { get; } = model;
public Bitmap? CachedImage { get; set; }
public string AuthorName => Model.Author.DisplayName;
public IBrush? AuthorColor
@@ -40,22 +38,14 @@ public sealed class MessageViewModel(MessageModel model) : ViewModelBase
public string? ServerUrl => Model.ServerUrl;
public MessageType Type => Model.Type;
public Dictionary<string, Bitmap> ImageCache { get; } = [];
public bool HasAttachment => Model.AttachmentUrl is not null;
public IReadOnlyList<AttachmentDto> Attachments => Model.Attachments;
public string? AttachmentFileName => Model.AttachmentFileName;
public string? AttachmentUrl => Model.AttachmentUrl;
public bool IsImage => Type == MessageType.Image;
public bool HasAttachments => Attachments.Count > 0;
public bool ShowContent => !string.IsNullOrEmpty(Content);
public bool IsAudio => Type == MessageType.Audio;
public bool IsFile => Type == MessageType.File;
public string TimeText
{
get
+24 -16
View File
@@ -1,6 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Decho.ViewModels"
xmlns:dto="using:EchoHub.Core.DTOs"
x:Class="Decho.Views.MessageItemView"
x:DataType="vm:MessageViewModel">
<Grid Margin="5" ColumnDefinitions="Auto, *">
@@ -27,22 +28,29 @@
VerticalAlignment="Top"
TextWrapping="Wrap"
IsVisible="{Binding ShowContent}" />
<Image x:Name="MessageImage"
MaxHeight="300"
MaxWidth="400"
HorizontalAlignment="Left"
IsVisible="{Binding IsImage}"
Margin="0 4 0 0" />
<StackPanel IsVisible="{Binding HasAttachment}" Margin="0 4 0 0" Spacing="2">
<TextBlock Text="{Binding AttachmentFileName}"
FontSize="11"
Opacity="0.7" />
<Button Content="Download"
Click="OnDownloadClicked"
FontSize="11"
Padding="5 1"
HorizontalAlignment="Left" />
</StackPanel>
<ItemsControl ItemsSource="{Binding Attachments}"
IsVisible="{Binding HasAttachments}"
Margin="0 4 0 0">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="dto:AttachmentDto">
<StackPanel Margin="0 2 0 2">
<Image Loaded="OnAttachmentImageLoaded"
MaxHeight="300"
MaxWidth="400"
HorizontalAlignment="Left" />
<TextBlock Text="{Binding FileName}"
FontSize="11"
Opacity="0.7" />
<Button Content="Download"
Click="OnAttachmentDownloadClicked"
FontSize="11"
Padding="5 1"
HorizontalAlignment="Left"
Margin="0 2 0 0" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
+37 -24
View File
@@ -8,6 +8,7 @@ using Avalonia.Platform.Storage;
using Decho.ViewModels;
using EchoHub.Core.DTOs;
using EchoHub.Core.Models;
using System.Text.RegularExpressions;
@@ -22,7 +23,6 @@ public partial class MessageItemView : UserControl
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
Loaded += OnLoaded;
}
private static readonly Regex MentionRegex = new(@"@(\w+)", RegexOptions.Compiled);
@@ -36,11 +36,11 @@ public partial class MessageItemView : UserControl
_loadCts?.Cancel();
_loadCts = new CancellationTokenSource();
this.FindControl<Image>("MessageImage")?.ClearValue(Image.SourceProperty);
_loadedMessageId = null;
if (DataContext is MessageViewModel msg)
{
_loadedMessageId = msg.Model.Id;
BuildMessageInlines(msg.Content);
}
}
@@ -78,31 +78,40 @@ public partial class MessageItemView : UserControl
}
}
private void OnLoaded(object? sender, RoutedEventArgs e)
private async void OnAttachmentImageLoaded(object? sender, RoutedEventArgs e)
{
if (DataContext is not MessageViewModel msg || !msg.IsImage || msg.AttachmentUrl is null)
if (sender is not Image image)
{
return;
}
if (msg.Model.Id == _loadedMessageId)
if (image.DataContext is not AttachmentDto att)
{
return;
}
_loadedMessageId = msg.Model.Id;
if (msg.CachedImage is not null)
if (att.Kind != AttachmentKind.Image)
{
this.FindControl<Image>("MessageImage")!.Source = msg.CachedImage;
return;
}
_ = LoadImageAsync(msg, _loadCts!.Token);
}
if (DataContext is not MessageViewModel msg)
{
return;
}
CancellationTokenSource? cts = _loadCts;
if (cts is null)
{
return;
}
if (msg.ImageCache.TryGetValue(att.Url, out Bitmap? cached))
{
image.Source = cached;
return;
}
private async Task LoadImageAsync(MessageViewModel msg, CancellationToken ct)
{
try
{
MainWindowViewModel? mainVm = this.GetMainWindowViewModel();
@@ -112,9 +121,9 @@ public partial class MessageItemView : UserControl
}
byte[]? bytes = await mainVm.ConnectionService.DownloadImageBytesAsync(
msg.ServerUrl ?? "", msg.AttachmentUrl!);
msg.ServerUrl ?? "", att.Url);
ct.ThrowIfCancellationRequested();
cts.Token.ThrowIfCancellationRequested();
if (bytes is null || bytes.Length == 0)
{
return;
@@ -122,9 +131,9 @@ public partial class MessageItemView : UserControl
using MemoryStream stream = new MemoryStream(bytes);
Bitmap bitmap = new Bitmap(stream);
ct.ThrowIfCancellationRequested();
msg.CachedImage = bitmap;
this.FindControl<Image>("MessageImage")!.Source = bitmap;
cts.Token.ThrowIfCancellationRequested();
msg.ImageCache[att.Url] = bitmap;
image.Source = bitmap;
}
catch
{
@@ -162,15 +171,19 @@ public partial class MessageItemView : UserControl
}
}
private async void OnDownloadClicked(object? sender, RoutedEventArgs e)
private async void OnAttachmentDownloadClicked(object? sender, RoutedEventArgs e)
{
MessageViewModel? msg = this.GetDataContext<MessageViewModel>();
if (msg is null)
if (sender is not Button button)
{
return;
}
if (msg.AttachmentUrl is null)
if (button.DataContext is not AttachmentDto att)
{
return;
}
if (DataContext is not MessageViewModel msg)
{
return;
}
@@ -187,7 +200,7 @@ public partial class MessageItemView : UserControl
return;
}
string? tempPath = await mainVm.ConnectionService.DownloadAttachmentAsync(msg.ServerUrl ?? "", msg.AttachmentUrl, msg.AttachmentFileName ?? "download");
string? tempPath = await mainVm.ConnectionService.DownloadAttachmentAsync(msg.ServerUrl ?? "", att.Url, att.FileName);
if (tempPath is null)
{
@@ -196,7 +209,7 @@ public partial class MessageItemView : UserControl
IStorageFile? file = await topLevel.StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
SuggestedFileName = msg.AttachmentFileName ?? "download",
SuggestedFileName = att.FileName,
});
if (file?.TryGetLocalPath() is string savePath)