Menú lateral con opción de compartir

El objetivo

En esta ocasión añadiremos al ejercicio anterior la opción de compartir, de forma que desde nuestra aplicación podamos enviar la lista de tareas mediante un mensaje de WhatsApp, un correo electrónico, un SMS, etc.

Además, añadiremos el código necesario para disponer de un menú lateral donde podremos añadir más opciones, y donde sugerimos además colocar la opción de borrar la lista de tareas, para que no esté tan accesible desde la pantalla principal:

El menú lateral

Primero añadiremos en la barra superior de la pantalla principal de nuestra aplicación un botón para desplegar el menú lateral:

<ion-header>
  ...
  <ion-menu-button></ion-menu-button>
  ...    
</ion-header>

Como podemos leer en la documentación de IONIC, el elemento <ion-menu-button></ion-menu-button> se encarga de todo, ya que crea automáticamente el icono del botón y añade la funcionalidad necesaria para desplegar el menú en la página actual.

Sólo nos falta añadir el código necesario con el menú lateral, que tendrá su propio encabezado y una lista con las opciones que deseemos. En nuestro caso pondremos la opción de reordenar, de compartir y de borrado de todos los elementos de la lista seleccionada. Además, añadiremos también un botón para volver a ocultar el menú:

<ion-menu>
  <ion-header>
    ...
    <ion-menu-button></ion-menu-button>
    ...
  </ion-header>
  <ion-content>
    <ion-menu-toggle>
      <ion-list>
        <ion-item-divider><ion-label>Reorder and Share</ion-label></ion-item-divider>   
        <ion-item (click)="toggleReorder()">
          <span *ngIf="reorder">Disable reorder</span>
          <span *ngIf="!reorder">Enable reorder</span>
          <ion-icon slot="end" name="reorder"></ion-icon>
        </ion-item>
        <ion-item (click)="share()">
          Share<ion-icon slot="end" name="share"></ion-icon>
        </ion-item>
        ...
      </ion-list>
    </ion-menu-toggle>
  </ion-content>
</ion-menu>
<ion-router-outlet main></ion-router-outlet>

Observaremos que el elemento <ion-menu></ion-menu> también se encarga de todo. Por defecto creará un menú que aparezca desde la izquierda de la pantalla actual, tal como se especifica en la documentación de IONIC. Se podrá ocultar pulsando de nuevo un botón de menú, o con un gesto swipe hacia la izquierda, o incluso pulsando fuera del menú. Además, utilizaremos el elemento <ion-menu-toggle></ion-menu-toggle> para conseguir que al pulsar en cualquier opción, el menú también se cierre automáticamente.

Aprovechando además el potencial que nos proporciona Angular con la directiva ngIf (más detalles aquí), haremos que el texto de la opción de reordenar cambie en función de si se encuentra activada o no, utilizando el atributo reorder :

... 
<span *ngIf="reorder">Disable reorder</span>
<span *ngIf="!reorder">Enable reorder</span>
...

El fichero «home.page.html» completo

<ion-menu>
  <ion-header>
    <ion-toolbar color="danger">
      <ion-title>Menu</ion-title>
      <ion-buttons slot="primary">
          <ion-menu-button></ion-menu-button>
      </ion-buttons>
    </ion-toolbar>
  </ion-header>
  <ion-content>
    <ion-menu-toggle>
      <ion-list lines="full">
        <ion-item-divider><ion-label>Reorder and Share</ion-label></ion-item-divider>   
        <ion-item (click)="toggleReorder()">
          <ion-icon slot="end" name="reorder"></ion-icon>
          <span *ngIf="reorder">Disable reorder</span>
          <span *ngIf="!reorder">Enable reorder</span>
        </ion-item>
        <ion-item (click)="share()">
          <ion-icon slot="end" name="share"></ion-icon>Share
        </ion-item>
        <ion-item-divider><ion-label>Delete</ion-label></ion-item-divider>
        <ion-item (click)="deleteItem()">
          <ion-icon slot="end" name="trash" color="danger"></ion-icon>Delete all
        </ion-item>
      </ion-list>
    </ion-menu-toggle>
  </ion-content> 
</ion-menu>
<ion-router-outlet main></ion-router-outlet>
<ion-header>
  <ion-toolbar color="primary">
    <ion-title>ToDo!</ion-title>
    <ion-buttons slot="primary">
      <ion-menu-button></ion-menu-button>
      <ion-button [routerLink]="['/AddEditItem', { tab:tabIndex, item:-1 }]"><ion-icon slot="icon-only" name="add"></ion-icon></ion-button>
    </ion-buttons>
  </ion-toolbar>       
</ion-header>
<ion-content>
  <ion-tabs #myTabs color="primary">
    <ion-tab *ngFor="let tab of tabs; let i=index" [label]="tab.label" [icon]="tab.icon" (ionSelect)="setTab(i)">
      <ion-list #myList lines="full">
        <ion-reorder-group [disabled]="!reorder" (ionItemReorder)="moveItem($event.detail)">
          <ion-item-sliding *ngFor="let item of tabs[i].list; let j=index">
            <ion-item [routerLink]="['/AddEditItem', { tab:i, item:j }]">
              <ion-label text-wrap>
                <h2>{{item.task}}</h2>
                <p>{{item.date}}</p>
              </ion-label>
              <ion-icon slot="end" [name]="item.icon"></ion-icon>
              <ion-reorder slot="end"></ion-reorder>
            </ion-item>
            <ion-item-options side="start">
              <ion-item-option color="danger" (click)="deleteItem(j)">
                <ion-icon slot="icon-only" name="trash"></ion-icon>
              </ion-item-option>
            </ion-item-options>
          </ion-item-sliding>
        </ion-reorder-group>
      </ion-list>
    </ion-tab>
  </ion-tabs>
</ion-content>

La opción de compartir

Utilizaremos la funcionalidad nativa de nuestros dispositivos móviles para poder enviar la lista de tareas a alguno de nuestros contactos. Para añadir el plugin necesario para acceder a dicha funcionalidad, nos colocaremos dentro de la carpeta de nuestro proyecto, y seguiremos los pasos indicados en la documentación de IONIC:

cd tareas
ionic cordova plugin add cordova-plugin-x-socialsharing
npm install --save @ionic-native/social-sharing@beta

Respecto a las modificaciones en el código fuente, en primer lugar deberemos añadir el import y la clase SocialSharing al array providers del archivo app.module.ts, tal como se indica en la documentación de IONIC:

...
import { SocialSharing } from '@ionic-native/social-sharing/ngx';

@NgModule({
  ...
  providers: [
    ...
    SocialSharing,
    ...
  ],
  ...
})
...

A continuación modificaremos el código fuente del archivo home.page.ts para añadir el método share que creará la cadena de texto con todas las tareas, y se la proporcionará al plugin para que pueda ser compartida:

import { SocialSharing } from '@ionic-native/social-sharing/ngx';
...
export class HomePage {
  ...
  constructor(private listService: ListService,
              private alertController: AlertController,
              private socialSharing: SocialSharing){
    ...
  }
  ...
  share() {
    let list:string = this.tabs[this.tabIndex].label + ":\n";
    this.tabs[this.tabIndex].list.forEach((task, index) => {
      list += (index+1) + ". " + task.task + " - " + task.date + "\n";
    });
    this.socialSharing.share(list);
  }
}

En resumen, en este archivo debemos realizar tres modificaciones. La primera de ellas es añadir el import:

import { SocialSharing } from '@ionic-native/social-sharing/ngx';

La segunda, modificar el constructor para recibir el código necesario de la clase SocialSharing:

constructor(private listService: ListService,
            private alertController: AlertController,
            private socialSharing: SocialSharing){
  ...
}

Y por último, crear el método share() cuya funcionalidad será obtener en una única cadena todas las tareas una detrás de otra, separadas por saltos de línea, y llamar a la función share del plugin para mostrar el menú de compartir de nuestros móviles:

share() {
  let list:string = this.tabs[this.tabIndex].label + ":\n";
  this.tabs[this.tabIndex].list.forEach((task, index) => {
    list += (index+1) + ". " + task.task + " - " + task.date + "\n";
  });
  this.socialSharing.share(list);
}

El archivo «app.module.ts» completo

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { SocialSharing } from '@ionic-native/social-sharing/ngx';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    SocialSharing,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

El archivo «home.page.ts» completo

import { Component, ViewChild } from '@angular/core';
import { Tabs, List, AlertController } from '@ionic/angular';
import { ListService } from '../list.service';
import { SocialSharing } from '@ionic-native/social-sharing/ngx';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})

export class HomePage {
  @ViewChild('myTabs') tabRef: Tabs;
  @ViewChild('myList') listRef: List;

  tabs: any;
  tabIndex: number;
  reorder: boolean;

  constructor(private listService: ListService,
              private alertController: AlertController,
              private socialSharing: SocialSharing){
    this.tabs = [
      {label: 'School', icon: 'school', list: []},
      {label: 'Home', icon: 'home', list: []}
    ];
    this.tabs.forEach((tab, index) => {
      tab.list = this.listService.getList(index);
    });
    this.tabIndex = 0;
    this.reorder = false;
  }

  ionViewDidEnter() {
    this.tabRef.select(this.tabIndex);
  }

  toggleReorder() {
    this.reorder = !this.reorder;
  }

  setTab(tabIndex) {
    this.tabIndex = tabIndex;
  } 

  async deleteItem(item?) {
    const alert = await this.alertController.create({
      header: item === undefined ? 'Delete all' : 'Delete item',
      message: 'Are you sure?',
      buttons: [
        {
          text: 'OK',
          handler: () => {
            this.listRef.closeSlidingItems();            
            if (item === undefined) {
              this.listService.deleteList(this.tabIndex);
            }
            else {
              this.listService.deleteItem(this.tabIndex, item);              
            }
          }
        },       
        {
          text: 'CANCEL',
          role: 'cancel'
        }
      ]
    });
    await alert.present();
  }

  moveItem(indexes) {
    this.listService.moveItem(this.tabIndex, indexes.from, indexes.to);
  }

  share() {
    let list:string = this.tabs[this.tabIndex].label + ":\n";
    this.tabs[this.tabIndex].list.forEach((task, index) => {
      list += (index+1) + ". " + task.task + " - " + task.date + "\n";
    });
    this.socialSharing.share(list);
  }
  
}

Compilando la aplicación con un solo comando

Debemos recordar que para probar la aplicación en nuestros dispositivos móviles, tenemos que compilar el código fuente. En este ejercicio vamos a utilizar sólo el cliente de IONIC, que nos permite generar el fichero APK en un único comando (más detalles aquí):

ionic cordova build android --prod

O también lo podemos ejecutar directamente  en el móvil que tengamos conectado a nuestro ordenador mediante el siguiente comando (más detalles aquí):

ionic cordova run android --prod

El resultado

Puedes hacer clic aquí para observar el aspecto final que tendrá la aplicación de la lista de tareas.