= Ionic = * https://ionicframework.com/ Ionic Framework is an open source mobile UI toolkit for building high quality, cross-platform native and web app experiences. It uses [[typescript]] and [[Angular]]. == QR code scanner == * https://github.com/bitpay/cordova-plugin-qrscanner * https://ionicframework.com/docs/v3/native/qr-scanner/ == Bar code scanner == * https://github.com/phonegap/phonegap-plugin-barcodescanner * https://ionicframework.com/docs/native/barcode-scanner == Run chrome == {{{#!highlight bash "/C/Program Files (x86)/Google/Chrome/Application/chrome.exe" --disable-web-security --disable-gpu --user-data-dir="c:\TempChrome" --disable-features=SameSiteByDefaultCookies,SameSiteDefaultChecksMethodRigorously # In Linux google-chrome --disable-web-security --disable-gpu --user-data-dir="/tmp" --disable-features=SameSiteByDefaultCookies,SameSiteDefaultChecksMethodRigorously }}} == Example app == * Requires Android SDK, node 12.16.2 and npm 6.14.5 {{{#!highlight bash cd ~ mkdir IonicTest npm install @ionic/cli node_modules/.bin/ionic start IonicTest #framework angular #starter template tabs #integrate capacitor N #create free ionic account N cd IonicTest npm i npm install @ionic/cli npm install cordova npm install cordova-res node_modules/.bin/ionic cordova plugin add cordova-plugin-advanced-http npm install @ionic-native/http node_modules/.bin/ionic cordova platform add android node_modules/.bin/ionic cordova build android scp ./platforms/android/app/build/outputs/apk/debug/app-debug.apk userx@example.net:/home/userx/www/ionic-test.apk node_modules/.bin/ionic cordova platform add browser node_modules/.bin/ionic cordova build browser node_modules/.bin/ionic serve --cordova --platform=browser # http://localhost:8100 # no CORS google-chrome --disable-web-security --disable-gpu --user-data-dir="/tmp" }}} === app.module.ts === {{{#!highlight javascript 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 { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { HTTP } from '@ionic-native/http/ngx'; import { FirebaseMessaging } from '@ionic-native/firebase-messaging/ngx'; import { Device } from '@ionic-native/device/ngx'; import { SQLite } from '@ionic-native/sqlite/ngx'; import { DbService } from './db.service'; import { NotifierService } from './notifier.service'; @NgModule({ declarations: [AppComponent], entryComponents: [], imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule], providers: [ HTTP, StatusBar, SplashScreen, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, FirebaseMessaging, Device, SQLite, DbService, NotifierService ], bootstrap: [AppComponent] }) export class AppModule { } }}} === app.component.ts === {{{#!highlight javascript import { Component } from '@angular/core'; import { Device } from '@ionic-native/device/ngx'; import { Platform } from '@ionic/angular'; import { SplashScreen } from '@ionic-native/splash-screen/ngx'; import { StatusBar } from '@ionic-native/status-bar/ngx'; import { FirebaseMessaging } from '@ionic-native/firebase-messaging/ngx'; import { HTTP } from '@ionic-native/http/ngx'; import { DbService } from './db.service'; import { NotifierService } from './notifier.service'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.scss'] }) export class AppComponent { constructor( private platform: Platform, private splashScreen: SplashScreen, private statusBar: StatusBar, private firebaseMessaging: FirebaseMessaging, private http: HTTP, private device: Device, private dbservice: DbService, private notifier: NotifierService ) { this.initializeApp(); } initializeApp() { this.platform.ready().then(() => { this.statusBar.styleDefault(); this.splashScreen.hide(); this.handleNotifications(); }); } private fcmGetToken() { this.firebaseMessaging.getToken().then((token: any) => { let url: string = "https://example.org/logit.php"; let body: any = { "devUUID": this.device.uuid }; console.log("Body:" + JSON.stringify(body)); this.http.post(url, body, {}).then(data => { console.log(data.status); console.log(data.data); // data received by server console.log(data.headers); // subscribe to topic with token name this.firebaseMessaging.subscribe(this.device.uuid); }) .catch(error => { alert("Error " + JSON.stringify(error.error)); }); }); } private handleNotifications() { this.firebaseMessaging.requestPermission().then(() => { this.fcmGetToken(); this.firebaseMessaging.onTokenRefresh().subscribe(() => { this.fcmGetToken(); }); this.firebaseMessaging.onMessage().subscribe((payload: any) => { this.dbservice.insertPush("fg msg " + payload.body); this.notifier.notify(NotifierService.GOT_PUSH); }); this.firebaseMessaging.onBackgroundMessage().subscribe((payload: any) => { this.dbservice.insertPush("back msg " + payload.body); this.notifier.notify(NotifierService.GOT_PUSH); }); }); } } }}} === tab1.page.ts === {{{#!highlight javascript import { Component } from '@angular/core'; import { HTTP } from '@ionic-native/http/ngx'; @Component({ selector: 'app-tab1', templateUrl: 'tab1.page.html', styleUrls: ['tab1.page.scss'] }) export class Tab1Page { public res; constructor(private http: HTTP) {} public ionViewDidEnter(){ console.log("Stuff"); this.http.get("https://labs.bitarus.allowed.org/xapps/rest/version/", {}, {}) .then(data => { console.log(data.status); console.log(data.data); // data received by server this.res=data.data; console.log(data.headers); }) .catch(error => { console.log(error.status); console.log(error.error); // error message as string console.log(error.headers); }); } } }}} === tab1.page.html === {{{#!highlight xml Tab 111 {{res}} Tab 111 }}} === tab2.page.html === {{{#!highlight xml Sum Sum Value 1 Value 2 Sum values }}} === tab2.page.ts === {{{#!highlight javascript import { Component } from '@angular/core'; import { AlertController } from '@ionic/angular'; @Component({ selector: 'app-tab2', templateUrl: 'tab2.page.html', styleUrls: ['tab2.page.scss'] }) export class Tab2Page { in1:string; in2:string; constructor(public alertController: AlertController) {} sumValuesButtonClicked(){ let res:Number; res = parseInt(this.in1) + parseInt(this.in2); this.presentResult(res).then(()=>{ console.log("Done"); }); } async presentResult(res:Number) { const alert = await this.alertController.create({ header: 'Sum', subHeader: 'Result', message: res.toString(), buttons: ['OK'], }); await alert.present(); let result = await alert.onDidDismiss(); console.log(result); } } }}} === tab3.page.html === {{{#!highlight xml Push notifications list Push notifications list }}} === notifier.service.ts === * ./node_modules/.bin/ionic generate service notifier {{{#!highlight javascript import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class NotifierService { static readonly GOT_PUSH: 'GotPush'; private actions: string[]; private callbacks: Function[]; constructor() { this.actions = []; this.callbacks = []; } /** * Subscribe a callback to an action * @param action * @param callback */ public subscribe(action: string, callback: Function) { this.actions.push(action); this.callbacks.push(callback); } public notify(action: string) { let idx: number; idx = -1; for (idx = 0; idx < this.actions.length; idx++) { if (this.actions[idx] == action) { break; } } if (idx != -1) { this.callbacks[idx](); } } } }}} === tab3.page.ts === {{{#!highlight javascript import { ChangeDetectorRef, Component } from '@angular/core'; import { DbService } from '../db.service'; import { NotifierService } from '../notifier.service'; @Component({ selector: 'app-tab3', templateUrl: 'tab3.page.html', styleUrls: ['tab3.page.scss'] }) export class Tab3Page { public items: string[]; constructor(private dbService: DbService, private notifier: NotifierService, private changeDetectionRef: ChangeDetectorRef ) { this.updateData(); this.notifier.subscribe(NotifierService.GOT_PUSH, this.gotPush.bind(this)); //setInterval(this.refresh.bind(this), 5000); } // private refresh() { // // triggers change detection when called by setInterval // } public gotPush() { try { this.updateData(); } catch (e) { alert("Caught error in gotPush"); } } public updateData() { this.dbService.getNotifications().then((data: string[]) => { this.items = data; this.changeDetectionRef.detectChanges(); }); } public ionViewDidEnter() { this.updateData(); } } }}} === db.service.ts === * ./node_modules/.bin/ionic generate service db {{{#!highlight javascript import { Injectable } from '@angular/core'; import { SQLite, SQLiteObject } from '@ionic-native/sqlite/ngx'; @Injectable({ providedIn: 'root' }) export class DbService { constructor(private sqlite: SQLite) { } private getDb(): Promise { let async: Promise = new Promise((resolve, reject) => { let promise = this.sqlite.create({ name: 'data.db', location: 'default' }); promise.then((db: SQLiteObject) => { db.executeSql('CREATE TABLE IF NOT EXISTS PushNotificationsTable (push text)', []) .then(() => { resolve(db); }) .catch((e) => { reject('Table not created ' + JSON.stringify(e)); }); }); promise.catch(e => reject("Create db error " + JSON.stringify(e))); }); return async; } public insertPush(body: string) { this.getDb().then((db: SQLiteObject) => { if (db != null) { db.executeSql('INSERT INTO PushNotificationsTable VALUES (?)', [body]) .then(() => { }) .catch(e => alert("Insert error " + JSON.stringify(e))); } else { alert('insertPush: this.db is null'); } }); } public getNotifications(): Promise { let asyncTask: Promise = new Promise((resolve, reject) => { this.getDb().then((db: SQLiteObject) => { if (db != null) { db.executeSql('SELECT * FROM PushNotificationsTable', []).then((data) => { let items: string[]; items = []; for (let i = 0; i < data.rows.length; i++) { let item = data.rows.item(i); items.push(item.push); } resolve(items); }).catch((e) => { reject(e); }); } else { alert('getNotifications: this.db is null'); reject('getNotifications: this.db is null'); } }); }); return asyncTask; } } }}} == EchoPlugin == * www/EchoPlugin.js {{{#!highlight javascript window.echo = function(str, callback) { cordova.exec(callback, function(err) { callback('Nothing to echo.'); }, "Echo", "echo", [str]); }; }}} * echo-plugin/plugin.xml {{{#!highlight xml echo-plugin echo plugin null }}} * echo-plugin/package.json {{{#!highlight javascript { "name": "org.allowed.bitarus.echoplugin", "version": "1.0.0", "description": "Echo plugin", "cordova": { "id": "org.allowed.bitarus.echoplugin", "platforms": [ "android" ] }, "keywords": [ "ecosystem:cordova", "cordova-android" ], "author": "Vitor", "license": "null" } }}} * echo-plugin/src/main/java/org/allowed/bitarus/EchoPlugin.java {{{#!highlight java package org.allowed.bitarus; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CallbackContext; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaInterface; public class EchoPlugin extends CordovaPlugin { @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); } @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if (action.equals("echo")) { String message = "Echo " + args.getString(0) + " !!!"; this.echo(message, callbackContext); return true; } if (action.equals("getintentdata")) { this.getintentdata(callbackContext); return true; } return false; } private void echo(String message, CallbackContext callbackContext) { if (message != null && message.length() > 0) { callbackContext.success(message); } else { callbackContext.error("Expected one non-empty string argument."); } } private void getintentdata(CallbackContext callbackContext) { String data = this.cordova.getActivity().getIntent().getDataString() ; String action = this.cordova.getActivity().getIntent().getAction(); String result = String.format( "{\"data\":\"%s\" , \"action\":\"%s\"}",data,action ); callbackContext.success(result); } } }}} * tab1.page.ts {{{#!highlight javascript import { Component } from '@angular/core'; import { HTTP } from '@ionic-native/http/ngx'; import { Platform } from '@ionic/angular'; declare let cordova: any; @Component({ selector: 'app-tab1', templateUrl: 'tab1.page.html', styleUrls: ['tab1.page.scss'] }) export class Tab1Page { public res; public echo: string; constructor(private http: HTTP, private platform: Platform) { this.platform.ready().then(() => { this.init(); } ); } public init() { this.getEcho(); } getEcho() { if (this.platform.is('cordova')) { try { cordova.exec( (success) => { this.echo = success; }, (error) => { }, //feature name in /home/vitor/MyIonicProject/echo-plugin/plugin.xml //feature name in /home/vitor/MyIonicProject/platforms/android/app/src/main/res/xml/config.xml "EchoPlugin", // class Service name "echo", // action ["argxyz"] // arguments ); } catch (e) { this.echo = "Got error in get echo " + JSON.stringify(e) + e.message; } } } public ionViewDidEnter() { console.log("Stuff"); this.http.get("https://labs.bitarus.allowed.org/version/", {}, {}) .then(data => { console.log(data.status); console.log(data.data); // data received by server this.res = data.data; console.log(data.headers); }) .catch(error => { console.log(error.status); console.log(error.error); // error message as string console.log(error.headers); }); } } }}} * tab1.page.html {{{#!highlight xml Tab 111 {{res}} Tab 111 }}} == SQLite == * npm install @ionic-native/sqlite * node_modules/.bin/ionic cordova plugin add cordova-sqlite-storage * https://github.com/storesafe/cordova-sqlite-storage {{{#!highlight javascript // app.module.ts import { SQLite } from '@ionic-native/sqlite/ngx'; providers SQLite // app.component.ts import { SQLite } from '@ionic-native/sqlite/ngx'; constructor private sqlite:SQLite }}} == Create page == * https://ionicframework.com/docs/cli/commands/generate * node_modules/.bin/ionic generate page test == Create service == * node_modules/.bin/ionic generate service notifier * Add in providers in app.module.ts * Inject in constructor of classes that might use the dependency/bean/service == Angular change detection == * https://www.mokkapps.de/blog/the-last-guide-for-angular-change-detection-you-will-ever-need/ {{{ In short, the framework will trigger a change detection if one of the following events occurs: any browser event (click, keyup, etc.) setInterval() and setTimeout() HTTP requests via XMLHttpRequest }}} * https://angular.io/api/core/ChangeDetectorRef {{{#!highlight javascript constructor(private ref: ChangeDetectorRef) { //... } this.ref.detectChanges(); // trigger detection change }}} == Setup environment debian bullseye == === setup_debian.sh === {{{#!highlight bash cd ~ rm -rf android/ rm -rf gradle-7.4.2/ rm -rf jdk8u222-b10/ wget https://downloads.gradle-dn.com/distributions/gradle-7.4.2-bin.zip unzip gradle-7.4.2-bin.zip sudo apt install -y curl sudo wget net-tools nodejs unzip wget https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u222-b10/OpenJDK8U-jdk_x64_linux_hotspot_8u222b10.tar.gz tar xvzf OpenJDK8U-jdk_x64_linux_hotspot_8u222b10.tar.gz wget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip mkdir ~/android unzip commandlinetools-linux-6858069_latest.zip mv cmdline-tools/ ~/android/ cd ~/android/cmdline-tools/ && mkdir latest && mv bin/ lib/ NOTICE.txt source.properties latest/ echo "export ANDROID_HOME=$HOME/android/" >> ~/.bashrc echo "export ANDROID_SDK_ROOT=$HOME/android/" >> ~/.bashrc echo "export PATH=/usr/local/bin:/usr/bin:/bin:$HOME/jdk8u222-b10/bin:$HOME/gradle-7.4.2/bin" >> ~/.bashrc echo "export JAVA_HOME=$HOME/jdk8u222-b10/" >> ~/.bashrc cat ~/.bashrc source ~/.bashrc $HOME/android/cmdline-tools/latest/bin/sdkmanager --update $HOME/android/cmdline-tools/latest/bin/sdkmanager --version yes | $HOME/android/cmdline-tools/latest/bin/sdkmanager --install "build-tools;30.0.3" $HOME/android/cmdline-tools/latest/bin/sdkmanager --list # should show installed packages }}} === setup_ionic_debian.sh === {{{#!highlight bash #!/bin/sh rm IonicTest -rf rm node_modules -rf npm install @ionic/cli node_modules/.bin/ionic start ITest --type angular tabs # angular, tabs, share anon data: no, create ionic account: no, cd ITest npm i npm i @ionic/cli npm i cordova npm i cordova-res node_modules/.bin/ionic integrations disable capacitor node_modules/.bin/ionic cordova plugin add cordova-plugin-advanced-http npm i @ionic-native/http npm i typescript npm i @angular/cli npm i @angular/core npm i @angular/compiler-cli npm i @angular/compiler npm i @angular/forms npm i @angular/common npm i @angular/platform-browser-dynamic npm i @angular/router npm i angular npm i @ionic/angular npm i @angular/platform-browser npm i zone.js node_modules/.bin/ionic cordova platform add android node_modules/.bin/ng add @ionic/cordova-builders # remove from platforms/android/gradlew the --illegal-access = permit # vi platforms/android/gradlew node_modules/.bin/ionic cordova build android # no to report statistics #node_modules/.bin/ionic cordova platform add browser #node_modules/.bin/ionic cordova build browser #node_modules/.bin/ionic serve --cordova --platform=browser #node_modules/.bin/ionic serve --cordova --platform=browser --host 0.0.0.0 --port 8081 # http://localhost:8081/ }}}