📌👀✨🦆💨🌴👉💥⚡🌼🔊🎵👌

📓 Convertir EbooK en APP (apk) !

📓 Convertir EbooK

 en APP (apk) !

Comment convertir un livre Gutenberg en une application Android

John Au-Yeung

Suivre

7 août 2019 · 10 min de lecture

Le projet Gutenberg a beaucoup de livres dans sa bibliothèque. Il stocke à la fois des livres du domaine public et des livres sous copyright. Ils sont disponibles dans une variété de formats que n'importe qui peut télécharger. Cependant, ils ne sont pas disponibles en tant qu'applications autonomes.

Il n'est pas trop difficile de le convertir en application. Vous pouvez y ajouter de nombreuses fonctionnalités comme le redimensionnement des polices, la mise en signet, la synthèse vocale, etc. Cependant, pour cet article, il restera simple. Il vous permet de lire le livre et de redimensionner les polices et d'enregistrer les paramètres de police.

Pour y parvenir, nous le construisons comme une application Ionic, qui utilise le framework Angular comme base. Il vous permet de créer une application Angular en tant qu'application Android avec des fonctionnalités natives.

Étant donné que notre application est un livre, elle ne nécessitera qu'une seule fonctionnalité native, à savoir l'accès à la base de données SQLite. Pour convertir le livre électronique de Gutenberg en application, nous extrayons d'abord le fichier ePub, puis nous chargeons le contenu extrait dans une base de données SQLite, vidons le contenu dans un fichier .sql, puis l'importons dans notre application à la charge via le plugin natif SQLitePorter pour Ionic.

Nous téléchargeons le livre Filthy Rich de Gutenberg et le convertissons en application Android.

ePub est juste une archive ZIP avec quelques métadonnées supplémentaires, nous pouvons donc l'extraire à l'aide de n'importe quel outil d'archivage. Après l'avoir extrait, vous devriez voir un tas de fichiers HTML. Ensuite, nous écrivons le script Python suivant:

import sys

import sqlite3

import glob

import os

de bs4 import BeautifulSoup

path = sys.argv [1]

print path

db_name = sys.argv [2]

db_path = '% s.db'% (db_name,)

conn = sqlite3.connect (db_path )

conn.text_factory = str

c = conn.cursor ()

c.execute ('' 'CRÉER UN TABLEAU SI NON EXISTE

            (id INTEGER PRIMARY KEY AUTOINCREMENT, numéro NUMERIC, content TEXT)' '')

c.execute ('DELETE FROM sections ')

i = 0

pour le nom de fichier en tri (glob.glob (os.path.join (path,' * .html ')), key = lambda f: int (filter (str.isdigit, f))):

   print filename

   f = open (filename, 'r')

   content = f.read ()

   soup = BeautifulSoup (content)

   body = soup.find ('body')

   links = soup.findAll ('a')

   images = body.findAll ('img')

   pour l'image dans les images:

       image ['src'] = image [ 'src']. replace (

           image ['src'], "./assets/%s"% (image ['src'],))

   pour un dans les liens:

       a.replaceWithChildren ()

   content = str (body)

   content = content.replace ('<body>', '<div>')

   .replace ( '</body>', '</div>') c.execute ("INSERT INTO sections (nombre, contenu) VALEURS (? ,?) ", (i, contenu))

   f.close ()

   conn.commit ()

   i + = 1

conn.close ()

Le script lit les fichiers HTML extraits et les charge dans la base de données SQLite, un fichier par entrée. Avant le chargement, nous remplaçons les balises body par une balise div normale car nous avons déjà une balise body dans notre application. De plus, nous avons changé les chemins de l'image en assetsdossier afin qu'ils puissent rester dans le assetsdossier de notre dossier de projet d'application.

Nous exécutons le script en exécutant:

C: \ epub-to-sqlite \ epub-to-sqlite.py C: \ epub-to-sqlite \ pg59849-images \ OEBPS crasseux

en supposant toutes vos affaires dans ces dossiers. Sinon, changez les chemins en conséquence. filthy-richest le nom de la base de données. C:\epub-to-sqlite\pg59849-images\OEBPSest l'emplacement des fichiers HTML de l'ePub.

Ensuite, nous ouvrons le fichier SQLite avec le programme DB Browser for SQLite, disponible sur https://sqlitebrowser.org . Là, nous ouvrons notre base de données SQLite que nous avons générée, puis nous cliquons sur le menu Fichier, puis cliquez sur Exporter, puis sur Base de données vers un fichier SQL, nous vidons la sectionstable. Conservez tout comme valeur par défaut. Assurez-vous que le dernier choix déroulant est 'Écraser l'ancien schéma (DROP TABLE, puis CREATE TABLE). Cela garantit que le contenu du livre est toujours importé lors du chargement.

Maintenant, nous pouvons écrire l'application. Si vous n'êtes pas familier avec la création d'applications Ionic, lisez https://medium.com/@hohanga/how-to-build-android-apps-with-ionic-framework-5e3c2fdf2a1 .

Nous échafaudons l'application comme d'habitude avec ionic new filthy-rich sidemenufilthy-richest le nom de l'application. Ensuite, nous prenons notre vidage SQLite et le copions dans un nouveau fichier dans le dossier de l'application. Appelez le nouveau fichier sql.ts.

Ensuite, nous devons créer de nouvelles pages. Exécutez ionic g component homePage ionic g component endPage et ionic g component sectionPage. Nous devons stocker notre état pour que la page en cours soit lue de manière centrale, nous devons donc l'utiliser @ngrx/store. Installez-le en exécutantnpm i @ngrx/store.

Ensuite, nous courons ng add @ngrx/store. Vous devriez voir un reducersdossier. Là, il devrait y avoir un index.tsfichier. Nous devons également créer un fichier pour notre réducteur pour stocker la page actuelle. Nous faisons cela en changeant le répertoire courant en reducersdossier et en l'exécutant ng g class pageReducer.

Nous exécutons notre application en exécutant ionic cordova run android -laprès avoir ouvert notre émulateur Genymotion comme indiqué dans https://medium.com/@hohanga/how-to-build-android-apps-with-ionic-framework-5e3c2fdf2a1 , où se -ltrouve le rechargement en direct.

Maintenant, nous ajoutons du code. Dans page-reducer.ts, nous ajoutons:

const SET_PAGE_START_END = 'SET_PAGE_START_END';

fonction pageReducer (état, action) {

   switch (action.type) {

       case SET_PAGE_START_END:

           state = action.payload;

           état de retour;

       par défaut:

           état de retour

   }

}

exportation {pageReducer, SET_PAGE_START_END};

En reducers/index.tsnous aurions dû:

importer {pageReducer} depuis './page-reducer';

export const reducers = {

 page: pageReducer

};

Dans home-page.component.ts, nous remplaçons ce qui est là par ce qui suit:

import {Component, OnInit} de ' @ angular / core ';

importer {SQLite, SQLiteObject} depuis ' @ionic -native / sqlite / ngx';

importer {Router} depuis ' @ angular / router ';

importer {SQLitePorter} depuis ' @ionic -native / sqlite-porter / ngx';

importer {sql} depuis '../sql';

importer {Platform} depuis ' @ ionic / angular ';

@Component ({

 selector: 'app-home-page',

 templateUrl: './home-page.component.html',

 styleUrls: ['./home-page.component.scss'],

})

classe d’exportation implémente HomePageComponent OnInit {

 sections:

 constructeur (

   sqlite privé: SQLite,

   routeur privé: routeur,

   sqlitePorter privé: SQLitePorter,

   plateforme privée: Platform

 ) {}

 ngOnInit () {

   this.router.events.subscribe ((event: any) => {

     if (! event.url ||! event.url.includes ('section')) {

       return;

     }

     const segments = event.url.split ('/');

     this.currentPage = segments [segments.length - 1];

     localStorage.setItem (' currentPage ', this.currentPage.toString ());

     this.currentPage = + localStorage.getItem (' currentPage ');

   })

   this.platform.ready (). then (() => {

     this.openDb ();

   } )

 }

 openDb () {

   this.sqlite.create ({

     nom: 'filthy-rich.db',

     emplacement: 'default',

     createFromLocation: 1

   })

     .then ((db: SQLiteObject) => {

       this.populateSections (db);

     })

     .catch (e => console.log (e));

 }

 populateSections (db: SQLiteObject) {

   this.sqlitePorter.importSqlToDb (db, sql)

     .then (() => {

       this.getSections ();

     })

     .catch (e => console.error (e));

 }

 getSections () {

   try {

     this.sqlite.create ({

       name: 'filthy-rich.db',

       location: 'default',

     })

       .then ((db: SQLiteObject) => {

         db.executeSql ('select * from sections', [])

           .then ((data) => {

             this.sections = [];

             for (let i = 0; i < data.rows.length; i ++) {

               this.sections.push (data.rows.item (i));

             }

             this.sections = this.sections.map ((s: any, i) => {

               return {

                 id: s.id,

                 titre: `Section $ {s.number + 1}`,

                 url: s.number,

                 icon: 'book'

               }

             })

             this.currentPage = + localStorage.getItem ('currentPage');

           })

           .catch (e => console.log (e));

       })

       .catch (e => console.log (e));

   }

   catch (ex) {

     console.log (ex);

   }

 }

 goTo (id: number) {

   this.router.navigate (['section', id]);

 }

 isCurrentPage (pageNumber: number) {

   return pageNumber == this.currentPage;

 }

}

Nous avons utilisé le plugin SQLite pour ouvrir la base de données et charger chaque section et afficher les titres. currentPageest stocké dans le stockage local. En home-page.component.html, nous avons:

<ion-content [scrollEvents] = "true">

 <ion-list>

   <ion-item>

     <ion-icon slot = "start" name = 'home'> </ion-icon>

     <ion-label routerLink = = ' / home '>

       Accueil

     </ion-label>

   </ion-item>

   <ion-item>

     <ion-icon slot = "start" name =' settings '> </ion-icon>

     <ion-label routerLink =' / settings '>

       Settings

     </ion-label>

   </ion-item>

   <ion-item * ngFor =' let s of sections '(click) =' goTo (s.url) '[class.bold] = "isCurrentPage (s.url) ">

     <ion-icon slot =" start "[name] = "s.icon"> </ion-icon>

     <ion-label>

       {{s.title}}

     </ion-label>

   </ion-item>

 </ion-list>

</ion-content>

pour afficher les titres de chaque section et un lien vers la page d'accueil et les paramètres.

En end-page.component.html, nous avons:

<ion-content>

 <div class = "ion-text-center">

   <h1> La fin </h1>

 </div>

</ion-content>

En section-page.component.html, nous avons:

import {Component, OnInit} de ' @ angular / core ';

importer {SQLite, SQLiteObject} depuis ' @ionic -native / sqlite / ngx';

importer {ActivatedRoute, Router} depuis ' @ angular / router ';

importez {Store, sélectionnez} depuis ' @ ngrx / store ';

@Component ({

 selector: 'app-section-page',

 templateUrl: './section-page.component.html',

 styleUrls: ['./section-page.component.scss'],

})

classe d'exportation SectionPageComponent implémente OnInit {

 section: any = <any> {};

 currentPage: nombre = 0;

 page: any = <any> {};

 fontSize: nombre = 15;

   sqlite privé: SQLite,

   route privée: ActivatedRoute,

   magasin privé: Store <any>,

   routeur privé: Routeur

 ) {}

 ngOnInit () {

   this.route.params.subscribe (params => {

     const currentPage = + params ['currentPage' ];

     localStorage.setItem ('currentPage', currentPage.toString ());

     this.getSection (currentPage);

   })

   this.store.pipe (sélectionnez ('page'))

     .subscribe (page => {

       this.page = page;

     })

   if (! localStorage.getItem ('fontSize')) {

     localStorage.setItem ('fontSize', (15) .toString ());

   }

   this.fontSize = + (localStorage.getItem ('

fontSize ') ||

15);

 }

 getSection (currentPage: number) {

   try {

     this.sqlite.create ({

       name: 'filthy-rich.db',

       location: 'default',

     })

       .then ((db: SQLiteObject) => {

         db.executeSql ( `sélectionnez * dans les sections où number = $ {currentPage}`, [])

           .then ((data) => {

             this.section = data.rows.item (0);

             this.currentPage = this.section.number;

           } )

           .catch (e => console.log (e));

       })

       .catch (e => console.log (e));

   }

   catch (ex) {

     console.log (ex);

   }

 }

 goTo (id: numéro) {

   this.router.navigate (['/ section', id]);

 }

}

Nous obtenons la taille de la police et la page actuelle afin que nous puissions afficher la page actuelle et en fonction de la page à laquelle vous vous trouvez, les boutons précédent et suivant. Si vous êtes sur l'avant-dernière page, le bouton suivant ira à la page de fin. Sinon, il passera à la page suivante. La section de droite sera récupérée à partir du numéro de section dans le paramètre de requête.

section-page.component.html aura:

<ion-content>

   <div class = "section" [innerHtml] = 'section.content' [style.font-size.px] = 'fontSize'>

   </div>

</ion-content>

<ion-footer no -shadow>

   <ion-toolbar>

       <ion-button class = "ion-float-left" routerLink = '/ home' * ngIf = 'currentPage == 0'> Dernière page </ion-button>

       <ion-button class = "ion-float-left" (cliquez) = 'goTo (currentPage - 1)' * ngIf = 'currentPage> 0'> Dernière page

       </ion-button>

       <ion-button class = "ion-float-right" (click) = 'goTo (currentPage + 1)' * ngIf = 'currentPage <page? .end -1'> Page suivante

       </ion-button>

       <ion-button class = "ion-float-right" routerLink = '/ end' * ngIf = 'currentPage == page? .end -1'> Page suivante </ion-button>

   </ion-toolbar>

</ion-footer>

pour afficher le contenu.

En app.component.ts, nous avons:

importer {Component} depuis ' @ angular / core ';

importer {Platform} depuis ' @ ionic / angular ';

importer {SplashScreen} depuis ' @ionic -native / splash-screen / ngx';

importer {StatusBar} depuis ' @ionic -native / status-bar / ngx';

importer {SQLite, SQLiteObject} depuis ' @ionic -native / sqlite / ngx';

importer {SQLitePorter} depuis ' @ionic -native / sqlite-porter / ngx';

importer {sql} à partir de './sql';

importer {Router, ActivatedRoute} depuis ' @ angular / router ';

importer {Store} depuis ' @ ngrx / store ';

import {AdMobFree, AdMobFreeBannerConfig} de ' @ionic -native / admob-free / ngx';

const bannerConfig: AdMobFreeBannerConfig = {

 isTesting: false,

 autoShow: true,

 id: 'ca-app-pub-9346223375875688/7329501627'

};

@Component ({

 selector: 'app-root',

 templateUrl: 'app.component.html'

})

classe d'exportation AppComponent {

 public appPages = [];

 currentPage: nombre;

 constructeur (

   plate-forme privée: plate-forme,

   écran de démarrage privé: écran de démarrage, barre d'état

   privée: barre d'état,

   sqlite privée: SQLite,

   sqlitePorter privé: SQLitePorter,

   routeur privé: routeur,

   magasin privé: magasin <any>

 ) {

   this.initializeApp ();

 }

 initializeApp () {

   this.platform.ready (). then (() => {

     this.statusBar.styleDefault ();

     this.splashScreen.hide ();

     this.openDb ();

   });

   this.router.events.subscribe ((event: any) => {

     if (! event.url ||! event.url.includes ('section')) {

       return;

     }

     const segments = event.url.split (' / ');

     this.currentPage = segments [segments.length - 1];

     localStorage.setItem (' currentPage ', this.currentPage.toString ());

   })

}

 openDb () {

   this.sqlite.create ({

     name: 'filthy-rich.db',

     location: 'default',

     createFromLocation: 1

   })

     .then ((db: SQLiteObject) => {

       this.populateSections (db);

     })

     .catch (e => console.log (e));

 }

 populateSections (db: SQLiteObject) {

   this.sqlitePorter.importSqlToDb (db, sql)

     .then (() => {

       this.getSections ();

     })

     .catch (e => console.error (e));

}

 getSections () {

   try {

     this.sqlite.create ({

       name: 'filthy-rich.db',

       location: 'default',

     })

       .then ((db: SQLiteObject) => {

         db.executeSql ('select * from sections', [])

           .then ((data) => {

             this.appPages = [];

             for (let i = 0; i < data.rows.length; i ++) {

               this.appPages.push (data.rows.item (i));

             }

             this.appPages = this.appPages.map ((p: any, i) => {

               return {

                 title: `Section $ {p.number + 1}`,

                 url: p.number,

                 icône: 'book',

                 id: p.id

               }

             })

             this.currentPage = + localStorage.getItem ('currentPage');

             this.store.dispatch ({

               type: SET_PAGE_START_END, charge utile: {

                 début: 0,

                 fin: data.rows.length

               }

             })

           })

           .catch (e => console.log (e));

       })

       .catch (e => console.log (e));

   }

   catch (ex) {

     console.log (ex);

   }

 }

 goTo (id: number) {

   this.router.navigate (['/ section', id]);

 }

 isCurrentPage (pageNumber: number) {

   return pageNumber == this.currentPage;

 }

}

Ceci est le point d'entrée de notre application, nous chargeons donc toutes les données dans la base de données avec SQLitePorter car il n'y a aucun moyen d'utiliser une base de données avec des données préchargées.

En app.component.html, nous avons:

<ion-app>

 <ion-split-pane>

   <ion-menu type = "overlay">

     <ion-header>

       <ion-toolbar>

         <ion-title> Menu </ion-title>

       </ion-toolbar>

     </ion-header>

     <ion-content>

       <ion-menu-toggle auto-hide = "false">

         <ion-list>

           <ion-item>

             <ion-icon slot = "start" name = 'home'> </ion-icon>

             <ion-label routerLink = '/ home'>

               Accueil

             </ion-label>

           </ion-item>

           <ion-item>

             <ion-icon slot = "start" name = 'settings'> </ion-icon>

             <ion-label routerLink = '/ settings'>

               Paramètres

             </ion-label>

           </ion-item>

           <ion-item * ngFor = 'let p of appPages' (click) = 'goTo (p.url)' [class.bold] = "isCurrentPage (p.url ) ">

             <ion-icon slot =" start "[name] =" p.icon "> </ion-icon>

             <ion-label>

               {{p.title}}

             </ion-label>

           </ ion- item>

         </ion-list>

       </ion-menu-toggle>

     </ion-content>

   </ion-menu>

   <div class = "ion-page" main>

     <ion-header>

       <ion-toolbar>

         <ion-buttons slot = "start">

           <ion-menu-button>

             <ion-icon name = "menu"> </ion-icon>

           </ion-menu-button>

         </ion-buttons>

         <ion-title> Filthy Rich par Fred Sheinbaum </ion-title>

       </ion-toolbar>

     </ion-header>

     <ion-content>

       <ion- routeur-prise principale> </ion-router-outlet>

     </ion-content>

   </div>

 </ion-split-pane>

</ion-app>

pour afficher notre menu latéral.

En app.module.ts, nous avons:

importer {NgModule} depuis ' @ angular / core ';

importer {BrowserModule} depuis ' @ angular / platform-browser ';

importer {RouteReuseStrategy} depuis ' @ angular / router ';

importer {IonicModule, IonicRouteStrategy} depuis ' @ ionic / angular ';

importer {SplashScreen} depuis ' @ionic -native / splash-screen / ngx';

importer {StatusBar} depuis ' @ionic -native / status-bar / ngx';

importer {AppComponent} depuis './app.component';

importer {AppRoutingModule} à partir de './app-routing.module';

importer {SQLite} depuis ' @ionic -native / sqlite / ngx';

-native / sqlite-porter / ngx ';

importer {HomePageComponent} depuis './home-page/home-page.component';

importer {SectionPageComponent} de './section-page/section-page.component';

importer {StoreModule} depuis ' @ ngrx / store ';

importer des {réducteurs} depuis './reducers';

importer {EndPageComponent} depuis './end-page/end-page.component';

importer {SettingsPageComponent} à partir de './settings-page/settings-page.component';

importer {FormsModule} depuis ' @ angular / forms ';

importer {AdMobFree} depuis ' @ionic -native / admob-free / ngx';

@NgModule ({

 déclarations: [

   AppComponent,

   HomePageComponent,

    SectionPageComponent,

   EndPageComponent,

   SettingsPageComponent

  ],

 entryComponents: [],

 importations: [

   BrowserModule,

   IonicModule.forRoot (),

   AppRoutingModule,

   StoreModule.forRoot (réducteurs),

   FormsModule

 ],

 fournisseurs: [

   StatusBar,

   SplashScreen,

   {provideategyStrope },

   SQLite,

   SQLitePorter

 ],

 bootstrap: [AppComponent]

})

classe d'exportation AppModule {}

En app.routing.module.ts, nous avons nos itinéraires:

importer {NgModule} depuis ' @ angular / core ';

importer {PreloadAllModules, RouterModule, Routes} depuis ' @ angular / router ';

importer {HomePageComponent} depuis './home-page/home-page.component';

importer {SectionPageComponent} de './section-page/section-page.component';

importer {EndPageComponent} depuis './end-page/end-page.component';

importer {SettingsPageComponent} à partir de './settings-page/settings-page.component';

const routes: Routes = [

 {

   path: '',

   redirectTo: 'home',

   pathMatch: 'full'

 },

 {

   path: 'home',

   composant: HomePageComponent

 },

    path:' section /: currentPage ',

   composant: SectionPageComponent

 },

 {

   path:' end ',

   composant: EndPageComponent

  },

 {

   path: 'settings',

   composant: SettingsPageComponent

 }

];

@NgModule ({

 importations: [

   RouterModule.forRoot (routes, {preloadingStrategy: PreloadAllModules})

 ],

 exportations: [RouterModule]

})

classe d'exportation AppRoutingModule {}

afin que nous puissions naviguer vers nos pages.

Enfin, nous avons une page de paramètres pour enregistrer la taille de la police pour le texte de notre livre.

En settings-page.component.ts, nous avons:

import {Component, OnInit} de ' @ angular / core ';

@Component ({

 selector: 'app-settings-page',

 templateUrl: './settings-page.component.html',

 styleUrls: ['./settings-page.component.scss'],

})

classe d'exportation SettingsPageComponent implémente OnInit {

 fontSize: number;

 constructeur () {}

  ngOnInit () {

   if (! localStorage.getItem ('fontSize')) {

     localStorage.setItem ('fontSize', (15) .toString ());

   }

   this.fontSize = + (localStorage.getItem ('fontSize') || 15);

 }

 onChange () {

   localStorage.setItem ('fontSize', this.fontSize.toString ());

Ensuite, dans le settings-page.component.html, nous avons:

<ion-content>

 <ion-list>

   <ion-item>

     <h1> Paramètres </h1>

   </ion-item>

   <ion-item>

     <ion-label>

       Taille de police (px)

     </ion-label>

      <ion-label>

       <ion-range min = "10" max = "25" color = "secondary" [(ngModel)] = 'fontSize' name = 'fs' # fs = 'ngModel'

         (ionChange) = 'onChange () '>

         <ion-label slot = "start"> 10 </ion-label>

         <ion-label slot = "end"> 25 </ion-label>

       </ion-range>

     </ion-label>

   </ion-item>

 </ion-list>

</ion-content>

Un ion-rangecurseur est utilisé pour ajuster la taille de la police et l'enregistrer.

Enfin, nous copions les images de l'ePub extrait dans le assetsdossier afin que nous puissions voir les images.

À la fin, nous avons ceci:

Le produit final est publié sur https://play.google.com/store/apps/details?id=com.filthyrich.book

Remarque: créez uniquement des applications à partir de livres du domaine public comme celui-ci. La conversion de livres protégés par des droits d'auteur en applications est illégale.


Android avec le cadre ionique

Une solution simple pour créer des applications Web avec des composants angulaires

John Au-Yeung

John Au-Yeung

Suivre

4 août 2019 · 8 min de lecture

Photo de Stephen Frank sur Unsplash

La création d'applications Android peut être pénible. Vous devez apprendre le SDK Android à l'envers pour être productif.

Cependant, si vous avez déjà de l'expérience dans le développement d'applications Web modernes, la création d'applications Android peut être amusante et facile.

Le framework Ionic fournit aux développeurs frontaux un moyen simple de créer leurs applications Web à l'aide des composants angulaires fournis par les développeurs Ionic.

La version 4 d'Ionic comprend également des composants pour React et Vue.js , vous pouvez donc utiliser votre framework frontal préféré pour créer des applications Android.

Ionic est utilisé pour créer des applications. Il comprend des composants pour les entrées, les grilles, le défilement, les sélecteurs de date et d'heure, les cartes et d'autres composants courants des applications Android.

Dans cette pièce, nous allons construire un convertisseur de devises.


Création de l'application

Pour commencer à construire, nous pouvons utiliser la CLI d'Ionic. Si vous avez utilisé la CLI d'Angular, elle devrait vous être familière.

Pour installer l'Ionic CLI, exécutez npm install -g ionic.

Ensuite, pour générer le code squelette de notre application, exécutez ionic start currency-converter-mobile sidemenu.

currency-converter-mobileest le nom de notre application et sidemenuspécifie que nous voulons un menu latéral dans notre application.

Nous devons également installer ngx-custom-validatorspour la validation du formulaire. Nous avons besoin de ce package car la validation de la plage de numéros n'est pas intégrée dans Angular.

Après cela, nous pouvons exécuter ionic servepour voir notre application dans le navigateur. L'onglet du navigateur devrait s'actualiser automatiquement lorsque nous apportons des modifications.

Maintenant, nous pouvons commencer à écrire du code pour le convertisseur de devises.

Courez ng g service currency.

Cela va créer currency.service.ts.

Dans ce fichier, nous ajoutons:

import {Injectable} de ' @ angular / core ';

importer {HttpClient} depuis ' @ angular / common / http';

const APIURL = ' https://api.exchangeratesapi.io' ;

@Injectable ({

 providedIn: 'root'

})

classe d'exportation CurrencyService {

constructeur (

   httpClient privé: HttpClient

 ) {}

getLatest () {

   return this.httpClient.get (`$ {APIURL} / latest`);

 }

getExchangeRate (from: string, to: string) {

   return this.httpClient.get (`$ {APIURL} / latest? base = $ {from} & symboles = $ {to}`);

 }

}

Cela nous permet d'obtenir des données de change sur https://exchangeratesapi.io/ .

Ensuite, exécutez ionic g component convertPageet ionic g component homePageajoutez notre formulaire de conversion de devises et notre page d'accueil, respectivement.

La page d'accueil est le point d'entrée de notre application Android, tout comme une application Web classique.

Ensuite, dans app-routing.module.ts, nous changeons le code en:

importer {NgModule} depuis ' @ angular / core ';

importer {PreloadAllModules, RouterModule, Routes} depuis ' @ angular / router ';

importer {HomePageComponent} depuis './home-page/home-page.component';

import {ConvertPageComponent} de './convert-page/convert-page.component';

const routes: Routes = [

 {

   path: '',

   redirectTo: 'home',

   pathMatch: 'full'

 },

 {

   path: 'home',

   composant: HomePageComponent

 },

 {

   path: 'convert',

   composant: ConvertPageComponent

 }

];

@NgModule ({

 importe:

   RouterModule.forRoot (routes, {preloadingStrategy: PreloadAllModules})

 ],

 exportations: [RouterModule]

})

classe d'exportation AppRoutingModule {}

Dans app.component.ts, nous remplaçons ce qui est là par:

importer {Component} depuis ' @ angular / core ';

importer {Platform} depuis ' @ ionic / angular ';

importer {SplashScreen} depuis ' @ionic -native / splash-screen / ngx';

importer {StatusBar} depuis ' @ionic -native / status-bar / ngx';

importer {environnement} depuis 'src / environnements / environnement';

@Component ({

 selector: 'app-root',

 templateUrl: 'app.component.html'

})

classe d'exportation AppComponent {

 public appPages = [

   {

     title: 'Home',

     url: '/ home',

     icon: 'home'

   },

   {

     title:

     url: '/ convertir',

     icône: 'cash'

   }

 ];

 constructeur (

   plateforme privée: plateforme,

   splashScreen privé: SplashScreen,

   private statusBar: StatusBar,

 ) {

   this.initializeApp ();

 }

 initializeApp () {

   this.platform.ready (). then (() => {

     this.statusBar.styleDefault ();

     this.splashScreen.hide ();

   });

 }

}

Et, dans app.component.html, nous remplaçons le code par défaut par:

<ion-app>

 <ion-split-pane>

   <ion-menu type = "overlay">

     <ion-header>

       <ion-toolbar>

         <ion-title> Menu </ion-title>

       </ion-toolbar>

     </ion-header>

     <ion-content>

       <ion-list>

         <ion-menu-toggle auto-hide = "false" * ngFor = "let p of appPages">

           <ion-item [routerDirection] = "'root '"[routerLink] =" [p.url] ">

             <ion-icon slot =" start "[name] =" p.icon "> </ion-icon>

             <ion-label>

               {{p.title} }

             </ion-label>

           </ion-item>

         </ion-menu-toggle>

       </ion-list>

     </ion-content>

   </ion-menu>

   <div class = "ion-page" main>

     <ion-header>

       <ion-toolbar>

         <ion-buttons slot = "start">

           <ion-menu-button>

             <ion-icon name = "menu"> </ion-icon>

           </ion-menu-button>

         </ion-buttons>

         <ion-title> Convertisseur de devises </ion-title>

       </ion-toolbar>

     </ion-header>

     < ion-content>

       <ion-router-outlet main> </ion-router-outlet>

     </ion-content>

   </div>

 </ion-split-pane>

</ion-app>

Dans convert-page.component.ts, nous remplaçons ce que nous avons par:

import {Component, OnInit, ChangeDetectorRef} de ' @ angular / core ';

importer des {devises comme c} à partir de '../monnaies';

importer {CurrencyService} à partir de '../currency.service';

importer {NgForm} depuis ' @ angular / forms ';

importer {ToastController} depuis ' @ ionic / angular ';

importez {Store, sélectionnez} depuis ' @ ngrx / store ';

const devises = c;

@Component ({

 selector: 'app-convert-page',

 templateUrl: './convert-page.component.html',

 styleUrls: ['./convert-page.component.scss'],

 fromCurrencies: any [] = Object.assign ([], devises);

 toCurrencies: any [] = Object.assign ([], devises);

 résultat: any = <any> {};

 showResult: booléen;

constructeur (

   private currencyService: CurrencyService,

   public toastController: ToastController,

   private store: Store <any>

 ) {

   store.pipe (select ('recentCoversion'))

     .subscribe (recentCoversion => {

       if (! recentCoversion) {

         return;

       }

       this. currencyOptions = recentCoversion;

     });

 }

ngOnInit () {}

setToCurrencies (event) {

   if (! event.detail ||! event.detail.value) {

     return;

   }

   this.toCurrencies = Object.assign ([], currencies.filter (c => c.abbreviation! = event.detail.value));

 }

setFromCurrencies (event) {

   if (! event.detail ||! event.detail.value) {

     return;

   }

   this.fromCurrencies = Object.assign ([], currencies.filter (c => c.abbreviation! = event.detail.value));

 }

convert (convertForm: NgForm) {

   this.showResult = false;

   if (convertForm.invalid) {

     return;

   }

   this.currencyService.getExchangeRate (this.currencyOptions.from, this.currencyOptions.to)

     .subscribe ((res: any) => {

       let recentConversions = [];

       if (localStorage.getItem ('recentConversions')) {

         recentConversions = JSON.parse (localStorage.getItem ('recentConversions'));

       }

       recentConversions.push (this.currencyOptions);

       recentConversions = Array.from (nouvel ensemble (recentConversions));

       localStorage.setItem ('recentConversions', JSON.stringify (recentConversions));

       const rate = res.rates [this.currencyOptions.to];

       this.result = + this.currencyOptions.amount * rate;

       this.showResult = true;

     }, err => {

       this.showError ();

     })

 }

async showError () {

   const toast = attendez this.toastController.create ({

     message: 'Taux de change introuvable.',

     durée: 2000

   });

   toast.present ();

 }

}

Et, dans convert-page.component.html, nous remplaçons ce que nous avons par:

<ion-content [scrollEvents] = "true">

 <form # convertForm = 'ngForm' (ngSubmit) = 'convert (convertForm)'>

   <ion-list>

     <ion-item no-padding>

       <h1> Convert Currency < / h1>

     </ion-item>

     <aucun élément de remplissage sans

       ion > <ion-label> Montant </ion-label>

       <ion-input [(ngModel)] = 'currencyOptions.amount' name = 'amount' # amount = 'ngModel' required [min] = '0'> </ion-input>

     </ion-item>

     <ion-item no-padding>

       <ion-label> Devises à convertir depuis </ion-label>

       < ion-select [(ngModel)] = 'currencyOptions.from' name = 'from'# from = 'ngModel' required

         (ionChange) = 'setToCurrencies ($ event)' (ionBlur) = 'setToCurrencies ($ event)'>

         <ion-select-option [value] = 'c.abbreviation' * ngFor = 'let c of fromCurrencies; laissez i = index '>

           {{c.currency}}

         </ion-select-option>

       </ion-select>

     </ion-item>

     <ion-item no-padding>

       <ion-label> Devises à convertir </ion-label>

       <ion-select [(ngModel)] = 'currencyOptions.to' name = 'to' # to = 'ngModel' required

         (ionChange) = 'setFromCurrencies ($ event)' (ionBlur) = 'setFromCurrencies ($ event) '>

         <ion-select-option [value] =' c.abbreviation '* ngFor =' let c of toCurrencies; laissez i = index '>

           {{c.currency}}

         <

   <ion-button type = 'submit'> Convert </ion-button>

   <ion-list * ngIf = 'showResult'>

     <ion-item no-padding>

       {{currencyOptions.amount}} {{currencyOptions.from}} est {{result}} {{currencyOptions.to}}.

     </ion-item>

   </ion-list>

 </form>

</ion-content>

Le code ci-dessus fournira un formulaire permettant aux utilisateurs d'entrer leur montant en devise dans l' ion-inputélément. Ensuite, avec les deux ion-selectéléments, vous pouvez sélectionner la devise à convertir de et vers.

Il supprimera également la devise que vous avez déjà sélectionnée parmi les choix du menu déroulant auxquels aucune sélection n'est appliquée. Pour ce faire, vous devez gérer l' ionChangedévénement, puis supprimer la valeur sélectionnée dans le ion-select.

Si nous allons à http://localhost:8100/convert, nous voyons ce qui suit:

Puis, en home-page.component.ts:

import {Component, OnInit} de ' @ angular / core ';

importer {CurrencyService} à partir de '../currency.service';

importer {Store} depuis ' @ ngrx / store ';

importer {SET_RECENT_CONVERSION} depuis '../reducers/recent-coverions-reducer';

importer {Router} depuis ' @ angular / router ';

@Component ({

 selector: 'app-home-page',

 templateUrl: './home-page.component.html',

 styleUrls: ['./home-page.component.scss'],

})

classe d’exportation implémentations HomePageComponent OnInit {

 rates: any = <any> {};

 recentConversions: tout constructeur [] = []

(

   private currencyService:

   magasin privé: magasin <any>,

   routeur privé: routeur,

 ) {

   router.events.subscribe ((val) => this.recentConversions = this.getRecentConversions ())

 }

ngOnInit () {

   this.getLatest ();

   this.recentConversions = this.getRecentConversions ();

 }

getLatest () {

   this.currencyService.getLatest ()

     .subscribe (res => {

       this.rates = res;

     })

 }

}

Dans home-page.compone.html, passez à:

<ion-content [scrollEvents] = "true">

 <ion-list>

   <ion-item no-padding>

     <h1> Taux de change </h1>

   </ion-item>

   <ion-item no-padding * ngFor = 'let r of rates.rates | valeur-clé '>

     USD: {{r.key}} - 1: {{r.value}}

   </ion-item>

 </ion-list>

</ion-content>

Enfin, dans app.module.ts, remplacez ce que nous avons par:

importer {NgModule} depuis ' @ angular / core ';

importer {BrowserModule} depuis ' @ angular / platform-browser ';

importer {RouteReuseStrategy} depuis ' @ angular / router ';

importer {IonicModule, IonicRouteStrategy} depuis ' @ ionic / angular ';

importer {SplashScreen} depuis ' @ionic -native / splash-screen / ngx';

importer {StatusBar} depuis ' @ionic -native / status-bar / ngx';

importer {AppComponent} depuis './app.component';

importer {AppRoutingModule} à partir de './app-routing.module';

importer {HomePageComponent} depuis './home-page/home-page.component';

import {ConvertPageComponent} de './convert-page/convert-page.component';

importer {HttpClientModule} depuis ' @ angular / common / http';

importer {FormsModule} depuis ' @ angular / forms ';

importer {CustomFormsModule} à partir de 'ngx-custom-validators';

importer {CurrencyService} depuis './currency.service';

importer {StoreModule} depuis ' @ ngrx / store ';

importer des {réducteurs} depuis './reducers';

importer {AdMobFree} depuis ' @ionic -native / admob-free / ngx';

@NgModule ({

 déclarations: [

   AppComponent,

   HomePageComponent,

   ConvertPageComponent

 ],

 Importations: [

   BrowserModule,

   IonicModule.forRoot (),

   AppRoutingModule,

   HttpClientModule,

   FormsModule,

   CustomFormsModule

 ], les

 fournisseurs: [

   StatusBar,

   SplashScreen,

   {fournir: RouteReuseStrategy, useClass: IonicRouteStrategy},

   CurrencyService

 ],

 bootstrap: [AppComponent]

})

classe export AppModule {}

Vous devriez voir une liste des échanges en cours lorsque vous allez sur http://localhost:8100/home.


Test dans l'émulateur Genymotion

Il est facile de le tester dans Genymotion . Tout d'abord, ouvrez un compte Genymotion, téléchargez Genymotion et installez-le en suivant les instructions. Ensuite, exécutez le programme.

Une fois que vous avez ouvert Genymotion, ajoutez un émulateur en sélectionnant ce qui est disponible dans la section des modèles disponibles.

Une fois que vous ajoutez un émulateur, vous devriez voir quelque chose comme ceci:

Double-cliquez sur votre émulateur. Une fois que vous avez fait cela, vous devriez voir la fenêtre de l'émulateur. Exécutez ionic cordova run androiddans l'invite de commande Node pour voir l'application s'exécuter dans Genymotion.

Une fois la commande terminée, vous devriez voir:


Bâtiment pour la production

Tout d'abord, vous devez signer votre application. Pour ce faire, vous devez exécuter une commande pour générer keystore:

keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias

Exécutez ionic cordova build — prod — release androidpour créer l'application en mode production.

Après cela, vous pouvez le signer avec votre keystoreen exécutant:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore path / to / my-release-key.jks path / to / app-release-unsigned.apk alias_name

Optimisez l'APK en exécutant zipalign:

zipalign -v 4 app-release-unsigned.apk currency-converter-mobile.apk

Vous obtiendrez un fichier APK signé que vous pourrez soumettre aux magasins d'applications Android, tels que Google Play et Amazon.

Ionic facilite la création d'applications Android qui ne nécessitent pas trop de fonctionnalités natives, telles que l'interaction avec le matériel.

Avec les avancées récentes des API HTML pour interagir avec le matériel, comme les microphones, Ionic sera plus utile pour créer ce type d'applications.

Les performances sont décentes par rapport aux applications natives pour les applications simples et elles sont beaucoup plus faciles à développer.

Voir le produit final sur Google Play .


This blog post is actually just a Google Doc! Create your own blog with Google Docs, in less than a minute.