> For the complete documentation index, see [llms.txt](https://elguerre.gitbook.io/de0an/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://elguerre.gitbook.io/de0an/capitulo-10-la-publicacion/mejorando.md).

# Mejorando

Recordemos que los objetivos de la página principal, aún pendientes son:

1. Mostrar una barra de progreso y un mensaje mientras se cargan los datos.
2. El control *Hub*, podrá seguir interactuando con el usuario aún a pesar de no estar cargado al completo.
3. Carga incremental. De manera que sólo se cargará la información que el usuario necesite. Se obtendrá un número de elementos (20) y a medida que el usuario haga *scroll*, se irán obteniendo más.
4. Optimización de la interfaz de usuario para mejorar la experiencia visual y la usabilidad.

## Un VisualStateManager. "Cargando…"

El **VisualStateManager** es una herramienta de XAML que nos permite gestionar diferentes estados visuales de la interfaz de usuario. En nuestro caso, lo utilizaremos para manejar el estado de carga de datos en la página principal, proporcionando retroalimentación visual al usuario mientras la aplicación obtiene la información.

Definiremos dos estados: `Loading` y `Loaded`. El primero mostrará un panel con un `ProgressRing` y un mensaje "Cargando datos...", mientras que el segundo ocultará dicho panel y mostrará el contenido principal del *Hub*.

```xml
<Page
    x:Class="ElGuerre.Taskin.Uwp.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="LoadingStates">
                <VisualState x:Name="Loading">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LoadingPanel" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPanel" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Loaded">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="LoadingPanel" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPanel" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <!-- Panel de Carga -->
        <StackPanel x:Name="LoadingPanel" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
            <ProgressRing IsActive="True" Width="50" Height="50"/>
            <TextBlock Text="Cargando datos..." Margin="0,10,0,0" FontSize="16" HorizontalAlignment="Center"/>
        </StackPanel>

        <!-- Contenido Principal -->
        <StackPanel x:Name="ContentPanel" Visibility="Collapsed">
            <Hub x:Name="MainHub" HeaderTemplate="{Binding Source={StaticResource AppHeader}}">
                <!-- Secciones del Hub -->
            </Hub>
        </StackPanel>
    </Grid>
</Page>
```

Para cambiar de estado, en el *code-behind* de la página invocaremos `VisualStateManager.GoToState` al iniciar y finalizar la carga de datos:

```csharp
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.LoadData();
    }

    private async void LoadData()
    {
        VisualStateManager.GoToState(this, "Loading", true);
        await ViewModel.LoadDataAsync();
        VisualStateManager.GoToState(this, "Loaded", true);
    }
}
```

De esta forma, el usuario verá un indicador de carga mientras se obtienen los datos y, una vez completada la operación, se mostrará el contenido del *Hub* de manera fluida.

## Creando Acciones (Actions)

Las Acciones (o Actions), son eventos o comportamientos vinculados al interfaz de usuario en base a los cuales podemos responder extendiendo funcionalidad. Muchas de éstas ya se encuentran implementadas y disponibles en la DLL: "Behaviors SDK (XAML)", otros sin embargo, y por extraño que parezca, no han sido implementadas. Puede ocurrir también que éstas no cubran nuestras necesidades y por tanto tengamos que implementar una propia.

Sin ir más lejos, necesitaremos crear una Acción al trabajar con un control *Hub* en la página principal. Concretamente para conocer cuando el usuario se desplaza entre sus secciones. Aunque se trata de una acción básica y fundamental del control Hub, no se encuentra disponible para el patrón MVVM.

Como siempre, al trabajar con Comportamientos tendremos que tener incluidos los *namespaces* siguientes. De igual forma ocurrirá al trabajar con Acciones:

```xml
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
```

El siguiente código XAML, incluye la acción que nos permitirá conocer la primera de las secciones visible para el control *Hub*.

```xml
<Hub x:Name="MainHub" HeaderTemplate="{Binding Source={StaticResource AppHeader}}">
  <i:Interaction.Behaviors>
    <i:BehaviorCollection>
      <core:EventTriggerBehavior EventName="SectionsInViewChanged">
        <actions:HubSectionSelectedAction FirstSection="{Binding FirstHubSection, Mode=TwoWay}" />
      </core:EventTriggerBehavior>
    </i:BehaviorCollection>
  </i:Interaction.Behaviors>
  ...
</Hub>
```

El MainViewModel incluirá una propiedad "FirstHubSection" que almacenará dicho valor y la acción "HubSectionSelectedAction" su obtención.

La siguiente clase "HubSectionSelectedAction.cs", es la implementación de dicha acción.

```csharp
public sealed class HubSectionSelectedAction : DependencyObject, IAction
{
  public object FirstSection
  {
    get
    {
      return (object)GetValue(FirstSectionProperty);
    }
    set
    {
      SetValue(FirstSectionProperty, value);
    }
  }

  public static readonly DependencyProperty FirstSectionProperty =
    DependencyProperty.Register("FirstSection", typeof(object), typeof(HubSectionSelectedAction), new PropertyMetadata(null));

  public HubSectionSelectedAction()
  {
  }

  public object Execute(object sender, object parameter)
  {
    if (sender is Hub)
    {
      Hub hub = (Hub)sender;
      if (hub.SectionsInView.Count > 0)
      {
        FirstSection = hub.SectionsInView[0].Name;
      }
    }
    return null;
  }
}
```

## Behaviors en las secciones de Tareas y Pomodoros

Para mejorar la interacción del usuario con las secciones de **Tareas** y **Pomodoros**, implementaremos comportamientos adicionales que nos permitirán responder a eventos como el clic y el sostenido (*holding*) sobre cada elemento de la lista.

### Click

Mediante un `EventTriggerBehavior` vinculado al evento `Tapped`, permitiremos al usuario seleccionar una tarea para editarla. El comando `EditTaskCommand` del ViewModel recibirá como parámetro la tarea seleccionada:

```xml
<ListView ItemsSource="{x:Bind ViewModel.Tasks}" SelectionMode="None">
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Title}" FontSize="16" />
                <TextBlock Text="{Binding DueDate}" FontSize="12" Foreground="Gray" />
            </StackPanel>
            <Interactivity:Interaction.Behaviors>
                <Core:EventTriggerBehavior EventName="Tapped">
                    <Core:InvokeCommandAction Command="{Binding DataContext.EditTaskCommand, ElementName=PageRoot}" CommandParameter="{Binding}" />
                </Core:EventTriggerBehavior>
            </Interactivity:Interaction.Behaviors>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
```

### Holding

El evento *Holding* (sostenido) nos permite ofrecer acciones adicionales, como llamar o enviar un correo electrónico directamente desde la lista de tareas. Para ello, crearemos una acción personalizada `TaskHoldingAction` que recibe la tarea asociada:

```xml
<Interactivity:Interaction.Behaviors>
    <Core:EventTriggerBehavior EventName="Holding">
        <actions:TaskHoldingAction Task="{Binding}" />
    </Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
```

La implementación de dicha acción en C#:

```csharp
namespace ElGuerre.Taskin.Uwp.Actions
{
    public sealed class TaskHoldingAction : DependencyObject, IAction
    {
        public TaskItem Task
        {
            get => (TaskItem)GetValue(TaskProperty);
            set => SetValue(TaskProperty, value);
        }

        public static readonly DependencyProperty TaskProperty =
            DependencyProperty.Register("Task", typeof(TaskItem), typeof(TaskHoldingAction), new PropertyMetadata(null));

        public object Execute(object sender, object parameter)
        {
            if (Task != null)
            {
                EmailService.SendTaskEmail(Task);
            }
            return null;
        }
    }
}
```

### SemanticZoom y agrupación de tareas por proyectos

El **SemanticZoom** nos permite cambiar entre vistas de datos con diferentes niveles de detalle. En las secciones de Tareas y Pomodoros, lo utilizaremos para ofrecer una vista agrupada por proyectos (vista alejada) y una vista detallada de cada tarea (vista acercada):

```xml
<SemanticZoom>
    <SemanticZoom.ZoomedOutView>
        <ListView ItemsSource="{x:Bind ViewModel.GroupedTasks}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Key}" FontSize="20" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </SemanticZoom.ZoomedOutView>
    <SemanticZoom.ZoomedInView>
        <ListView ItemsSource="{x:Bind ViewModel.Tasks}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBlock Text="{Binding Title}" FontSize="16" />
                        <TextBlock Text="{Binding DueDate}" FontSize="12" Foreground="Gray" />
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </SemanticZoom.ZoomedInView>
</SemanticZoom>
```

En el ViewModel, agruparemos las tareas por proyecto mediante un `CollectionViewSource`, de manera que el *SemanticZoom* pueda alternar entre ambas vistas de forma automática:

```csharp
namespace ElGuerre.Taskin.Uwp.ViewModels
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<TaskItem> Tasks { get; set; } = new ObservableCollection<TaskItem>();
        public CollectionViewSource GroupedTasksView { get; set; }

        public MainViewModel()
        {
            GroupedTasksView = new CollectionViewSource
            {
                Source = Tasks
            };
            GroupedTasksView.IsSourceGrouped = true;
            GroupedTasksView.GroupDescriptions.Add(new PropertyGroupDescription("ProjectName"));
        }

        public async Task LoadTasksAsync()
        {
            var tasks = await DataService.GetTasksAsync();
            foreach (var task in tasks)
            {
                Tasks.Add(task);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string name = null) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}
```

De esta forma, el usuario podrá navegar rápidamente entre proyectos con un simple gesto de pellizco (*pinch*) y volver a la vista detallada seleccionando el proyecto que desee explorar.
