Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 10

Mejorando la experiencia de usuario - Parte 2

Este es el último post de esta serie, solo nos falta afinar estos dos importantes temas:

  • Detección de conexión a internet
  • Adición de la política de privacidad

Detección de conexión a internet

Cuando estamos llamando a la fuente RSS para armar los contenidos debemos controlar si hay o no conexión a internet para evitar que la aplicación se reviente.

En un artículo de este mismo blog enseñe a hacerlo:

Cómo detectar si hay conexión a Internet en WinRT C#

Ahora integraremos es funcionalidad con nuestra App.

Desde Visual Studio abrimos Util/RSSHelper.cs y adicionamos el código correspondiente al artículo mostrado anteriormente.

En el método GetArticleListFromFeedAsync hacemos las modificaciones necesarias para validar la conexión a internet justo antes de traer el feed

 public static async Task<ArticleList> GetArticleListFromFeedAsync(string feedUrl)
{
    var syncClient = new SyndicationClient();
    var lista = new ArticleList();

    if (InternetConectivity)
    {
        var feed = await syncClient.RetrieveFeedAsync(new Uri(feedUrl));
        foreach (var art in feed.Items)
        {
            var content = CreateContent(art.NodeValue);
            lista.Add(new Article()
                        {
                            Title = art.Title.Text,
                            Content = content,
                            Summary = CreateSummary(art.Summary, content),
                            ImgUri = Find1stImageFromHtml(content)
                        });
        }
    }

    return lista;
}

Y sino hay conexión...

Si no hay conexión es conveniente lanzar una excepción para que desde el View se atrape este error y se le de el tratamiento adecuado.

Definimos una variable con un texto relacionado a la excepción y cuando no haya conexión lanzamos la excepción correspondiente:

 
private const string INTERNET_REQUIRED = "Esta aplicación requiere acceso a internet para funcionar adecuadamente";

public static async Task<ArticleList> GetArticleListFromFeedAsync(string feedUrl)
{
    var syncClient = new SyndicationClient();
    var lista = new ArticleList();

    if (InternetConectivity)
    {
        var feed = await syncClient.RetrieveFeedAsync(new Uri(feedUrl));
        foreach (var art in feed.Items)
        {
            var content = CreateContent(art.NodeValue);
            lista.Add(new Article()
                        {
                            Title = art.Title.Text,
                            Content = content,
                            Summary = CreateSummary(art.Summary, content),
                            ImgUri = Find1stImageFromHtml(content)
                        });
        }
    }
    else
    {
        throw new Exception(INTERNET_REQUIRED);
    }

    return lista;
}

Necesitamos que cuando no haya conexión a internet se despliegue un mensaje informando al usuario de la situación. Desde Visual Studio, en la carpeta Util, creamos una nueva clase llamada MessageHelper que luce como se ve a continuación

 using System;
using System.Threading.Tasks;
using Windows.UI.Popups;

namespace RSSJuanK4Blog.Util
{
    public static class MessageHelper
    {
        public static async Task ShowMessageAsync(string content, string title)
        {
            MessageDialog msg;
            if (!string.IsNullOrWhiteSpace(title))
                msg = new MessageDialog(content, title);
            else
                msg = new MessageDialog(content);

            msg.Options = MessageDialogOptions.AcceptUserInputAfterDelay;

            await msg.ShowAsync();
        }
    }
}

Ahora en ViewModel/RSSMainViewModel.cs método Initialize capturamos la posible excepción generada y recuperamos el mensaje de la misma.

Ya con el mensaje, lo evaluamos y si hay error entonces mostramos el MessageDialog y después de ello cerramos la aplicación:

 public async Task Initialize()
{
    string exMessage = "";
    try
    {
        Articles = await RSSHelper.GetArticleListFromFeedAsync(this.FeedUrlString);
    }
    catch (Exception e)
    {
        exMessage = e.Message;
    }

    if (!string.IsNullOrWhiteSpace(exMessage))
    {
        await MessageHelper.ShowMessageAsync(exMessage, "Houston, tenemos un problema!");
        App.Current.Exit();
    }

    FirstOrDefaultArticle = Articles.FirstOrDefault();
    IsLoading = false;
}

Mientras el mensaje se muestra debemos asegurarnos que la Lista no se muestre ni tampoco el ProgressRing para brindar una apariencia más agradable. El ProgressRing lo podemos cambiar haciendo uso de la propiedad IsLoading como lo vimos en anteriores artículos,para la Lista creamos una Propiedad ShowList de manera similar y ambas las asignamos cuando corresponda quedando el código así

 private bool _showList;

public bool ShowList
{
    get { return _showList; }
    set { SetProperty(ref _showList, value); }
}
 if (!string.IsNullOrWhiteSpace(exMessage))
{
    IsLoading = false;
    ShowList = false;
    await MessageHelper.ShowMessageAsync(exMessage, "Houston, tenemos un problema!");
    App.Current.Exit();
}
else {
    ShowList = true;
}

La parte lógica ya esta lista, ahora hay que integrarla la con el View, desde Visual Studio editamos Appx.xaml y adicionamos dos recursos nuevos al inicio, los cuales nos permiten convertir de booleano a la enumeración Visibility.

 <common:BooleanToVisibilityConverter x:Key="BoolToVisConv" />
<common:BooleanNegationConverter x:Key="NegBoolToVisConv" />

Desde luego para que lo anterior funcione hay que adicionar el namespace common en la parte superior del tag principal

 <Application
    x:Class="RSSJuanK4Blog.App"
    ...
    xmlns:common="using:RSSJuanK4Blog.Common"
    >

Ahora editamos View/RssMainPage.xaml, en el ListView lvwBlogPosts hacemos que el atributo Visibility haga Binding con la propiedad ShowList de la siguiente forma:

 <ListView x:Name="lvwBlogPosts" Grid.ColumnSpan="2" Grid.Row="1"
        Style="{StaticResource Lista-Posts-Style}" 
        ItemsSource="{Binding Articles}"
        ItemTemplate="{StaticResource Post-List-ItemTemplate}"
        SelectedItem="{Binding FirstOrDefaultArticle}"
        ItemContainerStyle="{StaticResource Post-List-ItemContainerTemplate}"
        Visibility="{Binding ShowList,Converter={StaticResource BoolToVisConv}}"
            />

SNAG-0000

 

Adición de la política de privacidad

Las aplicaciones que consumen o envían datos a internet deben incluir una política de privacidad.

Esta política debe informar al usuario acerca del uso que se le da a la información enviada por medio de la aplicación lo cual pueden ser desde URLs a las que accede hasta la recopilación de datos personales, dicha política debe ser fácilmente accesible/descubrible desde la propia aplicación.

Adicionalmente si dicha información se comparte con terceros, para cualquier fin, la aplicación debe contar con un mecanismo que autorice o deniegue dicho uso y este debe ser igualmente fácilmente accesible/descubrible.

Para el caso de la aplicación que se esta creando en este tutorial se debe incluir la política puesto que enviamos y recibimos datos por internet, que datos enviamos? actualmente solo los request hechos a la web pero dado que estos request son medibles y me permitirían utilizarlos para establecer por ejemplo las preferencias de un usuario.

La forma recomendada para mostrar la política es haciendo uso del Settings Charms, es un proceso sencillo, haremos uso de lo explicado en este artículo:

Como crear y/o utilizar el Charm de Settings en WinRT - C# - XAML

Así que básicamente debemos crear un control con la información de la política y atarlo al Charm, utilizaremos la funcionalidad de SettingsHelper del artículo citado.

En ViewModel/RssMainViewModel.cs vamos al método Initialize, es momento de hacer algo de refactoring, el código que esta actualmente en ese método tiene por objeto inicializar los componentes gráficos con la información del modelo, extraeremos esto en un método llamado UIInitializer y crearemos un nuevo método llamado SettingsInitializer .

 public async Task Initialize()
{
    await UIInitializer();
}

private async Task UIInitializer()
{
    string exMessage = "";
    try
    {
        Articles = await RSSHelper.GetArticleListFromFeedAsync(this.FeedUrlString);
    }
    catch (Exception e)
    {
        exMessage = e.Message;
    }

    if (!string.IsNullOrWhiteSpace(exMessage))
    {
        ShowList = false;
        IsLoading = false;
        await MessageHelper.ShowMessageAsync(exMessage, "Houston, tenemos un problema!");
        App.Current.Exit();
    }
    else
    {
        ShowList = true;
    }

    FirstOrDefaultArticle = Articles.FirstOrDefault();
    IsLoading = false;
}

Después de llamar a UIInitializer asignamos el manejador de evento y en el creamos la opción de menú.

 public async Task Initialize()
{
    await UIInitializer();
    SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;
}

private void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
{
    args.Request.ApplicationCommands.Clear();
    var jkCommand = new SettingsCommand("ppolicy", "Política de Privacidad",
                                        (handler) =>
                                        {
                                            var settingsHelper = new SettingsWindowHelper();
                                            settingsHelper.ShowFlyout(new PrivacyPolicyUC());

                                        });

    args.Request.ApplicationCommands.Add(jkCommand);
}

De lo anterior debemos crear el UserControl llamado PrivacyPolicyUC.

Desde Visual Studio creamos una nueva carpeta llamada "User Controls", a su vez dentro de ella creamos un nuevo UserControl llamado PrivacyPolicyUC.

En el colocamos un botón un título y un texto de la siguiente forma.

 <UserControl
    x:Class="RSSJuanK4Blog.User_Controls.PrivacyPolicyUC"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:RSSJuanK4Blog.User_Controls"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid  Background="#001E4E"
        Style="{StaticResource LayoutRootStyle}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Button Width="Auto" Height="Auto"  x:Name="CloseButton"
                Margin="10,10,0,0" Style="{StaticResource BackButtonStyle}"
                Click="CloseButton_Click"
                />
        <RichTextBlock Grid.Column="1" Grid.Row="0" Margin="0,20,20,0" 
                       Grid.RowSpan="2">
            <Paragraph FontSize="25" TextAlignment="Center" >
                <Run>Política de Privacidad</Run>
                <LineBreak/>
                <LineBreak/>
            </Paragraph>

            <Paragraph>
                Esta aplicación no recopila ni hace uso de la información del usuario, solo utiliza la conexión a internet para descargar contenidos del feed.
            </Paragraph>
        </RichTextBlock>
    </Grid>
</UserControl>

Agregamos este método en el code behind

 private void CloseButton_Click(object sender, RoutedEventArgs e)
{
    //Referenciar el Popup que es el control padre de este user control
    var pop = this.Parent as Popup;

    //Si el padre es en efecto un Popup cerrarlo
    if (pop != null)
        pop.IsOpen = false;

    //Mostrar el SettingsPane
    SettingsPane.Show();
}

Al ejecutarlo esta perfectamente funcional, solo que el WebView siempre parece estar por encima del UserControl:

SNAG-0001    

Para solucionar esto seguimos las recomendaciones dadas en este artículo:

El extraño caso del WebView que siempre mantiene visible sobre los demás controles – WinRT – C#

Por lo cual en RssMainView.xaml creamos un control de tipo Rectangle y su correspondiente estilo en Appx.xaml

 <Rectangle x:Name="wvWrapper" Grid.Column="2" Grid.Row="1" Style="{StaticResource wvWrapper-Style}"/>
 <Style x:Key="wvWrapper-Style" TargetType="Rectangle">
    <Setter Property="Margin" Value="10,0"/>
    <Setter Property="CacheMode" Value="BitmapCache"/>
</Style>

Aprovechando lo enseñado en el artículo del WebView y la funcionalidad del artículo de SettingsWindowHelper modificamos en RssMainViewModel.cs el método que inicializa los settings, para que se encargue de alternar entre el WebView verdadero y la copia en imagen según se requiera:

 private void MainPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
{
    args.Request.ApplicationCommands.Clear();
    var jkCommand = new SettingsCommand("ppolicy", "Política de Privacidad",
                (handler) =>
                {
                    var settingsHelper = new SettingsWindowHelper();

                    var brush = new WebViewBrush();
                    brush.SetSource(WebViewControl);
                    brush.Redraw();
                    wvWrapper.Fill = brush;

                    WebViewControl.Visibility = Visibility.Collapsed;

                    settingsHelper.ShowFlyout(new PrivacyPolicyUC(),
                        () => WebViewControl.Visibility = Visibility.Visible
                        );
                });

    args.Request.ApplicationCommands.Add(jkCommand);
}

SNAG-0002

   

Tareas adicionales

La app esta lista! en el archivo Package.appxmanifest de la solución podemos (aunque yo diría que DEBEMOS) configurar todas las diferentes imágenes necesarias para nuestra App y adicionar detalles como descripción etc.

Para que la publicación de la aplicación sea exitosa conviene revisar este artículo al respecto:

Check List para publicación de Apps en Windows Store

FIN DEL TUTORIAL

Eso es todo!

La solución completa pueden utilizarla como referencia o como prefieran y esta disponible aquí via GitHub:

jkRssReader

 

Índice General

Para cumplir con el alcance establecido he decidido fraccionar el proyecto en las siguientes partes:

 

  1. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 1
    • Introducción al tutorial  
    • Let's begin
    • Indice General
  2. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 2
    • Preparando la solución
  3. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 3
    • Modelo de Datos
  4. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 4
    • Consumiendo el RSS por medio de SyndicationClient
    • CreateContent
    • CreateSummary
    • Find1stImageFromHtml
  5. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 5
    • Inicializando la Aplicación e implementado el View-Model
    • Cómo y desde donde llamar a Initialize
    • Asociando el DataContext del View
  6. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 6
    • Construyendo la UI - Parte 1
    • Esquema principal de la App
    • Creando elementos básicos
    • El titulo
    • Aplicar propiedades utilizando estilos
    • El icono
    • El artículo actual
    • La Lista de Artículos
  7. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 7
    • Vinculando la View con el ViewModel
    • El artículo actual
  8. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 8
    • Mejorando la experiencia de usuario - Parte 1
    • Hacer que aparezca un articulo seleccionado por defecto
    • Disminuir el tamaño de los títulos del ListView
    • Disminuir el ancho del ListView
    • Evitar que los resúmenes de los artículos en el ListView crezcan de manera descontrolada
    • Colocar una imagen dummy en el Listview cuando no existan imágenes en el artículo
    • Colocar la imagen adecuada cuando la única imagen del RSS es el aggbug
    • Colocar una imagen dummy en el Listview cuando la imagen hallada en el artículo sea demasiado pequeña
    • Mientras cargan los datos del feed da la impresión de que la App no esta haciendo nada
    • Conclusión
  9. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 9
    • Mejorar la apariencia de ListView
    • Mejorar la apariencia del ProgressRing
    • Mejorar la apariencia del WebView
    • Soporte para Snapped View
    • Imagen de Fondo
  10. Windows 8:Cómo crear un app lector de blogs (RSS)-Parte 10
    • Mejorando la experiencia de usuario - Parte 2
    • Detección de conexión a internet
    • Adición de la política de privacidad
    • Tareas adicionales
    • FIN DEL TUTORIAL