mirror of
https://github.com/Stone-Red-Code/Decho.git
synced 2026-09-04 08:56:14 +02:00
Switch MessageModel to use attachments list and update related UI logic
This commit is contained in:
@@ -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;
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user