mirror of
https://github.com/Stone-Red-Code/Decho.git
synced 2026-09-04 00:46:11 +02:00
Add support for multiple file attachments in message composer
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Decho.Models;
|
||||||
|
|
||||||
|
public sealed record StagedFile(string FilePath, string FileName);
|
||||||
@@ -95,6 +95,57 @@ public sealed class ConnectionService : IDisposable
|
|||||||
await entry.Manager.SendMessageAsync(channelName, content);
|
await entry.Manager.SendMessageAsync(channelName, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SendMessageWithAttachmentsAsync(string serverUrl, string channelName, string content, IReadOnlyList<string> filePaths, string? size = null)
|
||||||
|
{
|
||||||
|
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Not connected to server");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Manager.RoomKeys.TryGetKey(channelName, out byte[]? roomKey);
|
||||||
|
List<OutgoingAttachment> attachments = new List<OutgoingAttachment>(filePaths.Count);
|
||||||
|
|
||||||
|
foreach (string filePath in filePaths)
|
||||||
|
{
|
||||||
|
string fileName = Path.GetFileName(filePath);
|
||||||
|
OutgoingAttachment attachment;
|
||||||
|
|
||||||
|
if (roomKey is not null && roomKey.Length > 0)
|
||||||
|
{
|
||||||
|
byte[] bytes = await File.ReadAllBytesAsync(filePath);
|
||||||
|
string declaredKind;
|
||||||
|
string? preview = null;
|
||||||
|
|
||||||
|
await using (MemoryStream ms = new MemoryStream(bytes))
|
||||||
|
{
|
||||||
|
if (FileValidationHelper.IsValidImage(ms))
|
||||||
|
{
|
||||||
|
declaredKind = "image";
|
||||||
|
(int w, int h) = ImageToAsciiService.GetDimensions(size);
|
||||||
|
ms.Position = 0;
|
||||||
|
preview = RoomCrypto.EncryptText(new ImageToAsciiService().ConvertToAscii(ms, w, h), roomKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
declaredKind = FileValidationHelper.IsAudioFile(fileName) ? "audio" : "file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encryptedBlob = RoomCrypto.EncryptBytes(bytes, roomKey);
|
||||||
|
attachment = new OutgoingAttachment(new MemoryStream(encryptedBlob), fileName, declaredKind, preview);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte[] bytes = await File.ReadAllBytesAsync(filePath);
|
||||||
|
attachment = new OutgoingAttachment(new MemoryStream(bytes), fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachments.Add(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = await entry.ApiClient.SendMessageWithAttachmentsAsync(channelName, content, attachments, size);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ChannelDto?> CreateChannelAsync(string serverUrl, string name, string? topic, bool isPublic, string? password = null)
|
public async Task<ChannelDto?> CreateChannelAsync(string serverUrl, string name, string? topic, bool isPublic, string? password = null)
|
||||||
{
|
{
|
||||||
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
if (!_connections.TryGetValue(serverUrl, out ServerConnection? entry))
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
Chat.Composer.SendRequested += HandleSendRequested;
|
Chat.Composer.SendRequested += HandleSendRequested;
|
||||||
Chat.Composer.CommandRequested += HandleCommandAsync;
|
Chat.Composer.CommandRequested += HandleCommandAsync;
|
||||||
Chat.Composer.FileUploadRequested += HandleFileUploadRequested;
|
|
||||||
Chat.LoadMoreRequested += HandleLoadMoreRequested;
|
Chat.LoadMoreRequested += HandleLoadMoreRequested;
|
||||||
|
|
||||||
WireCommandHandlerEvents();
|
WireCommandHandlerEvents();
|
||||||
@@ -707,13 +706,32 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
return result.Message;
|
return result.Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleSendRequested(string serverUrl, string text)
|
private void HandleSendRequested(string serverUrl, string text, IReadOnlyList<string> filePaths)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Chat.CurrentChannelName))
|
if (string.IsNullOrEmpty(Chat.CurrentChannelName))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filePaths.Count > 0)
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ConnectionService.SendMessageWithAttachmentsAsync(serverUrl, Chat.CurrentChannelName, text, filePaths);
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
|
StatusText = "Message sent");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
|
StatusText = $"Send failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_commandHandler.IsCommand(text))
|
if (_commandHandler.IsCommand(text))
|
||||||
{
|
{
|
||||||
_ = HandleCommandAsync(text);
|
_ = HandleCommandAsync(text);
|
||||||
@@ -736,29 +754,6 @@ public sealed class MainWindowViewModel : ViewModelBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleFileUploadRequested(string serverUrl, string filePath)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(Chat.CurrentChannelName))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ConnectionService.UploadFileAsync(serverUrl, Chat.CurrentChannelName, filePath, null);
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
|
||||||
StatusText = "File uploaded");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
|
||||||
StatusText = $"Upload failed: {ex.Message}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleCreateChannelRequested(ServerViewModel server)
|
private async Task HandleCreateChannelRequested(ServerViewModel server)
|
||||||
{
|
{
|
||||||
if (_mainWindow is null)
|
if (_mainWindow is null)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
using Decho.Models;
|
||||||
|
|
||||||
using EchoHub.Client.Commands;
|
using EchoHub.Client.Commands;
|
||||||
|
using EchoHub.Core.Constants;
|
||||||
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
@@ -7,16 +10,24 @@ namespace Decho.ViewModels;
|
|||||||
|
|
||||||
public sealed class MessageComposerViewModel : ViewModelBase
|
public sealed class MessageComposerViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
public event Action<string, string>? SendRequested;
|
public event Action<string, string, IReadOnlyList<string>>? SendRequested;
|
||||||
|
|
||||||
public event Func<string, Task<string?>>? CommandRequested;
|
public event Func<string, Task<string?>>? CommandRequested;
|
||||||
|
|
||||||
public event Action<string, string>? FileUploadRequested;
|
|
||||||
|
|
||||||
private readonly ObservableCollection<UserViewModel> _onlineUsers = [];
|
private readonly ObservableCollection<UserViewModel> _onlineUsers = [];
|
||||||
private readonly ObservableCollection<string> _channelNames = [];
|
private readonly ObservableCollection<string> _channelNames = [];
|
||||||
private CommandHandler? _commandHandler;
|
private CommandHandler? _commandHandler;
|
||||||
|
|
||||||
|
public ObservableCollection<StagedFile> StagedFiles { get; } = [];
|
||||||
|
|
||||||
|
public bool HasStagedFiles => StagedFiles.Count > 0;
|
||||||
|
|
||||||
|
public string StagedFilesSummary
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref field, value);
|
||||||
|
} = string.Empty;
|
||||||
|
|
||||||
public string Draft
|
public string Draft
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -51,7 +62,10 @@ public sealed class MessageComposerViewModel : ViewModelBase
|
|||||||
|
|
||||||
Autocomplete = new AutocompleteController([mentionProvider, channelProvider]);
|
Autocomplete = new AutocompleteController([mentionProvider, channelProvider]);
|
||||||
|
|
||||||
IObservable<bool> canSend = this.WhenAnyValue(x => x.Draft, draft => !string.IsNullOrWhiteSpace(draft));
|
IObservable<bool> canSend = this.WhenAnyValue(
|
||||||
|
x => x.Draft,
|
||||||
|
x => x.HasStagedFiles,
|
||||||
|
(draft, hasFiles) => !string.IsNullOrWhiteSpace(draft) || hasFiles);
|
||||||
SendCommand = ReactiveCommand.Create(Send, canSend);
|
SendCommand = ReactiveCommand.Create(Send, canSend);
|
||||||
|
|
||||||
_ = this.WhenAnyValue(x => x.Draft).Subscribe(OnDraftChanged);
|
_ = this.WhenAnyValue(x => x.Draft).Subscribe(OnDraftChanged);
|
||||||
@@ -75,12 +89,38 @@ public sealed class MessageComposerViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestFileUpload(string filePath)
|
public void StageFiles(IEnumerable<string> filePaths)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(ServerUrl))
|
int remaining = HubConstants.MaxAttachmentsPerMessage - StagedFiles.Count;
|
||||||
|
foreach (string path in filePaths)
|
||||||
{
|
{
|
||||||
FileUploadRequested?.Invoke(ServerUrl, filePath);
|
if (remaining <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StagedFiles.Any(f => f.FilePath == path))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StagedFiles.Add(new StagedFile(path, Path.GetFileName(path)));
|
||||||
|
remaining--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateStagedSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveStagedFile(StagedFile file)
|
||||||
|
{
|
||||||
|
_ = StagedFiles.Remove(file);
|
||||||
|
UpdateStagedSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearStagedFiles()
|
||||||
|
{
|
||||||
|
StagedFiles.Clear();
|
||||||
|
UpdateStagedSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetServer(string serverUrl, bool isConnected = true)
|
public void SetServer(string serverUrl, bool isConnected = true)
|
||||||
@@ -113,6 +153,14 @@ public sealed class MessageComposerViewModel : ViewModelBase
|
|||||||
Autocomplete.Reset();
|
Autocomplete.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateStagedSummary()
|
||||||
|
{
|
||||||
|
this.RaisePropertyChanged(nameof(HasStagedFiles));
|
||||||
|
StagedFilesSummary = StagedFiles.Count > 0
|
||||||
|
? $"{StagedFiles.Count} file(s) staged"
|
||||||
|
: string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDraftChanged(string? draft)
|
private void OnDraftChanged(string? draft)
|
||||||
{
|
{
|
||||||
Autocomplete.Update(draft ?? string.Empty);
|
Autocomplete.Update(draft ?? string.Empty);
|
||||||
@@ -121,20 +169,18 @@ public sealed class MessageComposerViewModel : ViewModelBase
|
|||||||
private void Send()
|
private void Send()
|
||||||
{
|
{
|
||||||
string text = Draft.Trim();
|
string text = Draft.Trim();
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Draft = string.Empty;
|
Draft = string.Empty;
|
||||||
|
|
||||||
if (_commandHandler is not null && _commandHandler.IsCommand(text))
|
if (_commandHandler is not null && _commandHandler.IsCommand(text))
|
||||||
{
|
{
|
||||||
_ = (CommandRequested?.Invoke(text));
|
_ = (CommandRequested?.Invoke(text));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
List<string> filePaths = StagedFiles.Select(f => f.FilePath).ToList();
|
||||||
SendRequested?.Invoke(ServerUrl, text);
|
StagedFiles.Clear();
|
||||||
}
|
UpdateStagedSummary();
|
||||||
|
|
||||||
|
SendRequested?.Invoke(ServerUrl, text, filePaths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,68 +1,113 @@
|
|||||||
<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:models="using:Decho.Models"
|
||||||
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
xmlns:i="https://github.com/projektanker/icons.avalonia"
|
||||||
x:Class="Decho.Views.MessageComposerView"
|
x:Class="Decho.Views.MessageComposerView"
|
||||||
x:DataType="vm:MessageComposerViewModel">
|
x:DataType="vm:MessageComposerViewModel">
|
||||||
<Grid>
|
<Grid RowDefinitions="Auto,*">
|
||||||
<Grid.ColumnDefinitions>
|
<Border IsVisible="{Binding HasStagedFiles}"
|
||||||
<ColumnDefinition Width="*" />
|
Background="{DynamicResource UiTheme02}"
|
||||||
<ColumnDefinition Width="Auto" />
|
CornerRadius="4"
|
||||||
<ColumnDefinition Width="Auto" />
|
Padding="6"
|
||||||
</Grid.ColumnDefinitions>
|
Margin="0 0 0 4">
|
||||||
<Grid>
|
<DockPanel>
|
||||||
<TextBox x:Name="MessageTextBox"
|
<Button DockPanel.Dock="Right"
|
||||||
HorizontalAlignment="Stretch"
|
Content="Clear"
|
||||||
Margin="0 0 5 0"
|
FontSize="11"
|
||||||
Text="{Binding Draft, Mode=TwoWay}"
|
Padding="4 0"
|
||||||
IsEnabled="{Binding IsConnected}"
|
HorizontalAlignment="Right"
|
||||||
KeyDown="OnTextBoxKeyDown">
|
Click="OnClearStagedClick" />
|
||||||
<TextBox.KeyBindings>
|
<ItemsControl ItemsSource="{Binding StagedFiles}"
|
||||||
<KeyBinding Command="{Binding SendCommand}" Gesture="Enter" />
|
VerticalAlignment="Center">
|
||||||
</TextBox.KeyBindings>
|
<ItemsControl.ItemsPanel>
|
||||||
</TextBox>
|
<ItemsPanelTemplate>
|
||||||
<Popup x:Name="MentionPopup"
|
<WrapPanel />
|
||||||
Placement="TopEdgeAlignedLeft"
|
</ItemsPanelTemplate>
|
||||||
PlacementTarget="{Binding #MessageTextBox}"
|
</ItemsControl.ItemsPanel>
|
||||||
IsOpen="{Binding Autocomplete.ShowPopup}"
|
<ItemsControl.ItemTemplate>
|
||||||
IsLightDismissEnabled="True"
|
<DataTemplate x:DataType="models:StagedFile">
|
||||||
MinWidth="100"
|
<Border Background="{DynamicResource UiTheme03}"
|
||||||
MaxHeight="140">
|
CornerRadius="3"
|
||||||
<ListBox ItemsSource="{Binding Autocomplete.FilteredItems}"
|
Padding="4 2"
|
||||||
SelectedIndex="{Binding Autocomplete.SelectedIndex}"
|
Margin="0 0 4 2">
|
||||||
BorderThickness="1"
|
<DockPanel>
|
||||||
BorderBrush="{DynamicResource UiTheme08}"
|
<TextBlock Text="{Binding FileName}"
|
||||||
Background="{DynamicResource UiTheme02}">
|
FontSize="11"
|
||||||
<ListBox.Styles>
|
VerticalAlignment="Center" />
|
||||||
<Style Selector="ListBoxItem:selected">
|
<Button Content="x"
|
||||||
<Setter Property="Background" Value="#3D3D3D" />
|
FontSize="10"
|
||||||
</Style>
|
Padding="2 0"
|
||||||
</ListBox.Styles>
|
Margin="4 0 0 0"
|
||||||
<ListBox.ItemTemplate>
|
Cursor="Hand"
|
||||||
<DataTemplate x:DataType="x:String">
|
Click="OnRemoveStagedClick" />
|
||||||
<TextBlock Text="{Binding}"
|
</DockPanel>
|
||||||
FontSize="12"
|
</Border>
|
||||||
Padding="4 2"
|
|
||||||
Cursor="Hand"
|
|
||||||
PointerPressed="OnMentionPointerPressed" />
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ListBox>
|
</ItemsControl>
|
||||||
</Popup>
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid>
|
||||||
|
<TextBox x:Name="MessageTextBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Margin="0 0 5 0"
|
||||||
|
Text="{Binding Draft, Mode=TwoWay}"
|
||||||
|
IsEnabled="{Binding IsConnected}"
|
||||||
|
KeyDown="OnTextBoxKeyDown">
|
||||||
|
<TextBox.KeyBindings>
|
||||||
|
<KeyBinding Command="{Binding SendCommand}" Gesture="Enter" />
|
||||||
|
</TextBox.KeyBindings>
|
||||||
|
</TextBox>
|
||||||
|
<Popup x:Name="MentionPopup"
|
||||||
|
Placement="TopEdgeAlignedLeft"
|
||||||
|
PlacementTarget="{Binding #MessageTextBox}"
|
||||||
|
IsOpen="{Binding Autocomplete.ShowPopup}"
|
||||||
|
IsLightDismissEnabled="True"
|
||||||
|
MinWidth="100"
|
||||||
|
MaxHeight="140">
|
||||||
|
<ListBox ItemsSource="{Binding Autocomplete.FilteredItems}"
|
||||||
|
SelectedIndex="{Binding Autocomplete.SelectedIndex}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource UiTheme08}"
|
||||||
|
Background="{DynamicResource UiTheme02}">
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem:selected">
|
||||||
|
<Setter Property="Background" Value="#3D3D3D" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="x:String">
|
||||||
|
<TextBlock Text="{Binding}"
|
||||||
|
FontSize="12"
|
||||||
|
Padding="4 2"
|
||||||
|
Cursor="Hand"
|
||||||
|
PointerPressed="OnMentionPointerPressed" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Height="33"
|
||||||
|
Width="33"
|
||||||
|
Margin="0 0 5 0"
|
||||||
|
Content="Send"
|
||||||
|
i:Attached.Icon="fa-paper-plane"
|
||||||
|
Command="{Binding SendCommand}"
|
||||||
|
IsEnabled="{Binding IsConnected}" />
|
||||||
|
<Button Grid.Column="2"
|
||||||
|
Height="33"
|
||||||
|
Width="33"
|
||||||
|
Click="OnFileUploadClicked"
|
||||||
|
i:Attached.Icon="fa-paperclip"
|
||||||
|
IsEnabled="{Binding IsConnected}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Button Grid.Column="1"
|
|
||||||
Height="33"
|
|
||||||
Width="33"
|
|
||||||
Margin="0 0 5 0"
|
|
||||||
Content="Send"
|
|
||||||
i:Attached.Icon="fa-paper-plane"
|
|
||||||
Command="{Binding SendCommand}"
|
|
||||||
IsEnabled="{Binding IsConnected}" />
|
|
||||||
<Button Grid.Column="2"
|
|
||||||
Height="33"
|
|
||||||
Width="33"
|
|
||||||
Click="OnFileUploadClicked"
|
|
||||||
i:Attached.Icon="fa-paperclip"
|
|
||||||
IsEnabled="{Binding IsConnected}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -4,6 +4,7 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
using Decho.Models;
|
||||||
using Decho.ViewModels;
|
using Decho.ViewModels;
|
||||||
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -134,15 +135,38 @@ public partial class MessageComposerView : UserControl
|
|||||||
|
|
||||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
{
|
{
|
||||||
AllowMultiple = false,
|
AllowMultiple = true,
|
||||||
Title = "Select a file to upload",
|
Title = "Select files to attach",
|
||||||
});
|
});
|
||||||
|
|
||||||
IStorageFile? file = files?.FirstOrDefault();
|
List<string> paths = [];
|
||||||
if (file?.TryGetLocalPath() is string path)
|
foreach (IStorageFile file in files)
|
||||||
{
|
{
|
||||||
vm.RequestFileUpload(path);
|
if (file.TryGetLocalPath() is string path)
|
||||||
|
{
|
||||||
|
paths.Add(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (paths.Count > 0)
|
||||||
|
{
|
||||||
|
vm.StageFiles(paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemoveStagedClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && button.DataContext is StagedFile file)
|
||||||
|
{
|
||||||
|
MessageComposerViewModel? vm = this.GetDataContext<MessageComposerViewModel>();
|
||||||
|
vm?.RemoveStagedFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClearStagedClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
MessageComposerViewModel? vm = this.GetDataContext<MessageComposerViewModel>();
|
||||||
|
vm?.ClearStagedFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDragOver(object? sender, DragEventArgs e)
|
private void OnDragOver(object? sender, DragEventArgs e)
|
||||||
@@ -164,14 +188,16 @@ public partial class MessageComposerView : UserControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
string? paths = e.Data.GetFiles()?
|
List<string> paths = e.Data.GetFiles()?
|
||||||
.Select(f => f.TryGetLocalPath())
|
.Select(f => f.TryGetLocalPath())
|
||||||
.FirstOrDefault(p => p is not null);
|
.Where(p => p is not null)
|
||||||
|
.Cast<string>()
|
||||||
|
.ToList() ?? [];
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
if (paths is string path)
|
if (paths.Count > 0)
|
||||||
{
|
{
|
||||||
vm.RequestFileUpload(path);
|
vm.StageFiles(paths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user