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