= 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

== 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
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 111 {{res}}
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 111</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 111 page {{res}}"></app-explore-container>
</ion-content>
}}}

=== tab2.page.html ===
{{{#!highlight xml
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Sum
    </ion-title>
  </ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Sum</ion-title>
    </ion-toolbar>
  </ion-header>
  <ion-item>
    <ion-label>Value 1</ion-label>
    <ion-input [(ngModel)]="in1"></ion-input>
  </ion-item>
  <ion-item>
     <ion-label>Value 2</ion-label>
     <ion-input [(ngModel)]="in2"></ion-input>
  </ion-item>
  <ion-button color="primary" (click)="sumValuesButtonClicked()">Sum values</ion-button>
</ion-content>
}}}


=== 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
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Push notifications list
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Push notifications list</ion-title>
    </ion-toolbar>
  </ion-header>
  <ul>
    <li *ngFor="let pushNotification of items">
      {{pushNotification}}
    </li>
  </ul>
</ion-content>
}}}

=== 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<SQLiteObject> {
    let async: Promise<SQLiteObject> = new Promise<SQLiteObject>((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<string[]> {
    let asyncTask: Promise<string[]> = new Promise<string[]>((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
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
    id="org.allowed.bitarus.EchoPlugin"
    version="1.0.0">
    <name>echo-plugin</name>
    <description>echo plugin</description>
    <license>null</license>

    <js-module src="www/EchoPlugin.js" name="EchoPlugin">
        <clobbers target="EchoPlugin" />
    </js-module>

    <platform name="android">
        <config-file target="res/xml/config.xml" parent="/*">
            <feature name="EchoPlugin" >
                <param name="android-package" value="org.allowed.bitarus.EchoPlugin"/>
            </feature>
        </config-file>
        <config-file target="AndroidManifest.xml" parent="/*">
            <uses-permission android:name="android.permission.INTERNET" />
        </config-file>
        <source-file src="src/main/java/org/allowed/bitarus/EchoPlugin.java" target-dir="src/org/allowed/bitarus" />
    </platform>
</plugin>
}}}


 * 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
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 111 {{res}}
    </ion-title>
  </ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 111</ion-title>
    </ion-toolbar>
  </ion-header>
  <app-explore-container name="Tab 111 page {{res}} {{echo}}"></app-explore-container>
</ion-content>
}}}

== 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/ 
}}}