using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using DodoSSH.Client.Android.Platform;
using DodoSSH.Client.Shell.ViewModels;
namespace DodoSSH.Client.Android.Views;
/// Designs 06 and 08 — one remote pane, over SFTP or over a bucket.
internal sealed partial class FilesScreen : UserControl
{
public FilesScreen() => AvaloniaXamlLoader.Load(this);
///
/// Opens a directory on a single tap.
///
///
///
/// The desktop pane opens on a double tap and on Enter. A phone has neither, so the gesture has to be
/// the one gesture there is — and that is why this is here rather than in the shared view model: making
/// selection navigate would give the desktop a single click that walks into a directory.
///
///
/// rather than a command on the row, because the row must stay a
/// plain : a button as the item template swallows the press before the list sees it
/// — it marks the pointer event handled, and the list's own handler is not registered for handled
/// events — leaving nothing selected and every command that reads the selection doing nothing. Tapped
/// fires after the list has moved its selection, which is what lets this read it.
///
///
/// A file is left selected rather than downloaded, and that is deliberate now rather than forced: the
/// actions below act on the selection, and a tap that started a transfer would make selecting a row to
/// read its size the same gesture as fetching it.
///
///
/// Tells the screen a row's tick moved, so the count above the listing keeps up.
///
/// The desktop's own handler, for the reason given there: the count is a property of the collection
/// rather than of any row, and each listing replaces every row it has. See
/// TransfersViewModel.RemoteChecksChanged.
///
/// An entry in the actions sheet was chosen: the sheet goes down as its command runs.
///
/// Three of the four open a question under the listing, and the sheet left over it would be the one thing
/// in the way of answering. The command is bound in the markup; this only lowers the sheet.
///
private void OnEntryMenuChosen(object? sender, RoutedEventArgs e)
{
if (DataContext is TransfersViewModel transfers)
{
transfers.IsEntryMenuOpen = false;
}
}
private void OnRemoteRowChecked(object? sender, RoutedEventArgs e) =>
(DataContext as TransfersViewModel)?.RemoteChecksChanged();
private void OnRemoteEntryTapped(object? sender, TappedEventArgs e)
{
if (DataContext is TransfersViewModel { SelectedRemoteEntry.IsNavigable: true } transfers)
{
transfers.OpenRemoteCommand.Execute(null);
}
}
///
/// Picks documents in the system picker and queues them for upload.
///
///
///
/// In the head rather than in the shared view model, for the reason every other platform difference is:
/// the picker is Android's, the staging directory is this application's cache, and the desktop reaches
/// its local files by browsing a pane that does not exist here. What crosses back into shared code is
/// what the queue understands — paths — through .
///
///
/// Failures land in the screen's own status line, which is where every other refusal on this
/// screen already is. The picker itself is a trip out to another application, and it can come back with
/// a document that has since been deleted or a grant that was revoked; the exception's message is more
/// use than "the upload failed", and a phone has nowhere else to put it.
///
///
private async void OnAddFiles(object? sender, RoutedEventArgs e)
{
if (DataContext is not TransfersViewModel transfers || TopLevel.GetTopLevel(this) is not { } top)
{
return;
}
try
{
var staged = await DocumentStaging.PickAsync(top, CancellationToken.None).ConfigureAwait(true);
if (staged.Count == 0)
{
return;
}
transfers.QueueStagedUploads(staged);
}
catch (Exception exception) when (exception is not OutOfMemoryException)
{
transfers.Status = $"Those files could not be read: {exception.Message}";
}
}
///
/// Asks where the chosen remote file should be saved, then queues it.
///
///
///
/// The other direction, and the asymmetry with is the platform's rather than
/// this screen's: coming in, several documents can be pointed at in one trip; going out, the save picker
/// names one destination, so this acts on the selected row. Asking for five destinations in a row to
/// download five files would be a worse screen than pressing the button five times.
///
///
/// Nothing is queued when the picker is dismissed. The document it makes when it is *not* dismissed
/// exists from that moment, which is why the queueing follows immediately — see
/// DocumentStaging.PickDestinationAsync.
///
///
private async void OnSaveFile(object? sender, RoutedEventArgs e)
{
if (DataContext is not TransfersViewModel transfers || TopLevel.GetTopLevel(this) is not { } top)
{
return;
}
// From the actions sheet as well as the bar, and the sheet goes down either way: the save picker is
// about to cover the screen, and coming back to the sheet still up would be coming back to a question
// already answered.
transfers.IsEntryMenuOpen = false;
if (transfers.SelectedRemoteEntry is not { IsFile: true } row)
{
transfers.Status = "Choose a file on the host to save.";
return;
}
try
{
if (await DocumentStaging.PickDestinationAsync(top, row.Name).ConfigureAwait(true)
is not { } destination)
{
return;
}
transfers.QueueDeliveredDownload(
row,
DocumentStaging.NewStagingPath(row.Name),
path => DocumentStaging.DeliverAsync(destination, path));
}
catch (Exception exception) when (exception is not OutOfMemoryException)
{
transfers.Status = $"That file could not be saved: {exception.Message}";
}
}
}