Compare commits
10 Commits
3b3353fff1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12fb3f27b4 | ||
|
|
5cdeb98493 | ||
|
|
c2c9ac6bb6 | ||
|
|
f4481d175e | ||
|
|
f90c564a3d | ||
|
|
b1c26baea0 | ||
|
|
0d99d94642 | ||
|
|
ac973b7288 | ||
|
|
96734e4f60 | ||
|
|
ef5d8e15d4 |
@@ -1,14 +0,0 @@
|
|||||||
package io.ionic.starter;
|
|
||||||
|
|
||||||
import com.getcapacitor.BridgeActivity;
|
|
||||||
|
|
||||||
import com.codetrixstudio.capacitor.GoogleAuth.GoogleAuth;
|
|
||||||
import android.os.Bundle; // required for onCreate parameter
|
|
||||||
|
|
||||||
public class MainActivity extends BridgeActivity {
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
registerPlugin(GoogleAuth.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
README.md
47
README.md
@@ -29,6 +29,50 @@ This readme acts as a reference for the mobis software and / or documentation.
|
|||||||
## Audience
|
## Audience
|
||||||
Scientists, (app) developers, project partners, people who are lookikng for low cost sensor integration solutions.
|
Scientists, (app) developers, project partners, people who are lookikng for low cost sensor integration solutions.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
More in depth documentation is available on ZENODO: https://zenodo.org/record/7615472#.Y-JidC8w2gQ
|
||||||
|
|
||||||
|
## Installation and setup
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js
|
||||||
|
- Ionic / capactior
|
||||||
|
|
||||||
|
- Git
|
||||||
|
- Xcode (for iOS)
|
||||||
|
- Android Studio (for Android)
|
||||||
|
|
||||||
|
A Parse server (for example the one provided by Pocket Science, DIY or Back4App) is required for the app to work.
|
||||||
|
A Firebase account for login and push notifications is required.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Rename the provided .app. constant example to app.copnstnt and fill in the required fields.
|
||||||
|
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install the dependencies
|
||||||
|
npm install
|
||||||
|
3. Add these entries to Android and ios
|
||||||
|
|
||||||
|
### Android manifest
|
||||||
|
|
||||||
|
android.permission.INTERNET
|
||||||
|
android.permission.ACCESS_COARSE_LOCATION
|
||||||
|
android.permission.ACCESS_FINE_LOCATION
|
||||||
|
android.permission.BLUETOOTH
|
||||||
|
android.permission.BLUETOOTH_SCAN
|
||||||
|
android.permission.BLUETOOTH_ADVERTISE
|
||||||
|
android.permission.CAMERA
|
||||||
|
android.permission.VIBRATE
|
||||||
|
android.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
|
### IOS capability
|
||||||
|
Add capabilities: Push notifications and Apple logon
|
||||||
|
Needed permissions (add to info.plist): camera, bluetooth, GPS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Generic
|
## Generic
|
||||||
Provided functionalities in R 1.0:
|
Provided functionalities in R 1.0:
|
||||||
@@ -42,9 +86,12 @@ Push notifications
|
|||||||
|
|
||||||
# Plugin description
|
# Plugin description
|
||||||
### General description
|
### General description
|
||||||
|
We created some sensor plugins for example. Sensors can be native (camera, GPS) or external (iSPEX and Canair.io)
|
||||||
|
|
||||||
|
|
||||||
## CANAIR.IO
|
## CANAIR.IO
|
||||||
### Description
|
### Description
|
||||||
|
Canair.io (see website) is a bluetooth peripheral to get PM2.5 and CO2 (depending on model) out. Connection is made via bluetooth.
|
||||||
|
|
||||||
## MINI SECCHI
|
## MINI SECCHI
|
||||||
### Description of the mobis Mini Secchi Plugin
|
### Description of the mobis Mini Secchi Plugin
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"plugins": {
|
"plugins": {
|
||||||
"GoogleAuth": {
|
"GoogleAuth": {
|
||||||
"scopes": ["profile", "email"],
|
"scopes": ["profile", "email"],
|
||||||
"clientId": "778522712103-tqkc468qermr7hvbqsmm2p2ueglcmjsi.apps.googleusercontent.com",
|
"clientId": "your key here.apps.googleusercontent.com",
|
||||||
"forceCodeForRefreshToken" : true
|
"forceCodeForRefreshToken" : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const config: CapacitorConfig = {
|
|||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
GoogleAuth: {
|
GoogleAuth: {
|
||||||
scopes: ['profile', 'email'],
|
scopes: ['profile', 'email'],
|
||||||
androidClientId: '778522712103-tqkc468qermr7hvbqsmm2p2ueglcmjsi.apps.googleusercontent.com',
|
androidClientId: '778522712103-put key here.apps.googleusercontent.com',
|
||||||
forceCodeForRefreshToken: true,
|
forceCodeForRefreshToken: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
// Protractor configuration file, see link for more information
|
|
||||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
|
||||||
|
|
||||||
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type { import("protractor").Config }
|
|
||||||
*/
|
|
||||||
exports.config = {
|
|
||||||
allScriptsTimeout: 11000,
|
|
||||||
specs: [
|
|
||||||
'./src/**/*.e2e-spec.ts'
|
|
||||||
],
|
|
||||||
capabilities: {
|
|
||||||
browserName: 'chrome'
|
|
||||||
},
|
|
||||||
directConnect: true,
|
|
||||||
SELENIUM_PROMISE_MANAGER: false,
|
|
||||||
baseUrl: 'http://localhost:4200/',
|
|
||||||
framework: 'jasmine',
|
|
||||||
jasmineNodeOpts: {
|
|
||||||
showColors: true,
|
|
||||||
defaultTimeoutInterval: 30000,
|
|
||||||
print: function() {}
|
|
||||||
},
|
|
||||||
onPrepare() {
|
|
||||||
require('ts-node').register({
|
|
||||||
project: require('path').join(__dirname, './tsconfig.json')
|
|
||||||
});
|
|
||||||
jasmine.getEnv().addReporter(new SpecReporter({
|
|
||||||
spec: {
|
|
||||||
displayStacktrace: StacktraceOption.PRETTY
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { AppPage } from './app.po';
|
|
||||||
|
|
||||||
describe('new App', () => {
|
|
||||||
let page: AppPage;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
page = new AppPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be blank', () => {
|
|
||||||
page.navigateTo();
|
|
||||||
expect(page.getParagraphText()).toContain('Start with Ionic UI Components');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { browser, by, element } from 'protractor';
|
|
||||||
|
|
||||||
export class AppPage {
|
|
||||||
navigateTo() {
|
|
||||||
return browser.get('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
getParagraphText() {
|
|
||||||
return element(by.deepCss('app-root ion-content')).getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../out-tsc/e2e",
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es2018",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-menu side="start" contentId="main">
|
<ion-menu side="end" contentId="main">
|
||||||
<ion-menu-toggle>
|
<ion-menu-toggle>
|
||||||
<ion-header color="primary">
|
<ion-header color="primary">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
|
|
||||||
<ion-item lines="none">
|
<ion-item lines="none">
|
||||||
<ion-label routerLink="home">M O B I S</ion-label>
|
<ion-title routerLink="home" class="ion-no-padding">M O B I S</ion-title>
|
||||||
|
<!-- <ion-label routerLink="home">M O B I S</ion-label> -->
|
||||||
<ion-buttons>
|
<ion-buttons>
|
||||||
<ion-button type="icon-only" (click)="closeMenu()"><ion-icon name="close-outline" color="dark"></ion-icon></ion-button>
|
<ion-icon name="close-outline" (click)="closeMenu()" size="large" slot="start"></ion-icon>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
@@ -15,25 +15,25 @@
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list lines="none" class="ion-no-padding">
|
||||||
<ion-list-header>
|
<ion-list-header>
|
||||||
<ion-label>{{ "menu.account" | translate }}</ion-label>
|
<ion-label>{{ "menu.account" | translate }}</ion-label>
|
||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
<ion-menu-toggle>
|
<ion-menu-toggle>
|
||||||
<ion-item button routerLink="signup" [disabled]="user">
|
<ion-item button routerLink="/signup" [disabled]="user">
|
||||||
<ion-icon name="person-add-outline"></ion-icon>
|
<ion-icon name="person-add-outline"></ion-icon>
|
||||||
<ion-label>{{ "menu.signup" | translate }}</ion-label>
|
<ion-label>{{ "menu.signup" | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
<ion-menu-toggle>
|
<ion-menu-toggle>
|
||||||
<ion-item button routerLink="login" [disabled]="user">
|
<ion-item button routerLink="/login" [disabled]="user">
|
||||||
<ion-icon name="log-in-outline"></ion-icon>
|
<ion-icon name="log-in-outline"></ion-icon>
|
||||||
<ion-label>{{ "menu.signin" | translate }}</ion-label>
|
<ion-label>{{ "menu.signin" | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-menu-toggle>
|
</ion-menu-toggle>
|
||||||
|
|
||||||
<ion-menu-toggle>
|
<ion-menu-toggle>
|
||||||
<ion-item button routerLink="profile" [disabled]="!user">
|
<ion-item button routerLink="/profile" [disabled]="!user">
|
||||||
<ion-icon name="person-circle-outline"></ion-icon>
|
<ion-icon name="person-circle-outline"></ion-icon>
|
||||||
<ion-label>{{ "menu.profile" | translate }}</ion-label>
|
<ion-label>{{ "menu.profile" | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<ion-label>{{ "menu.settings" | translate }}</ion-label>
|
<ion-label>{{ "menu.settings" | translate }}</ion-label>
|
||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
<ion-menu-toggle>
|
<ion-menu-toggle>
|
||||||
<ion-item button routerLink="options">
|
<ion-item button routerLink="/options">
|
||||||
<ion-icon name="settings-outline"></ion-icon>
|
<ion-icon name="settings-outline"></ion-icon>
|
||||||
<ion-label>{{ "menu.options" | translate }}</ion-label>
|
<ion-label>{{ "menu.options" | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
36
src/app/app.constant.example.ts
Normal file
36
src/app/app.constant.example.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export const ENV = {
|
||||||
|
production: false,
|
||||||
|
parseAppId: 'PARSE APP HERE',
|
||||||
|
parseServerUrl: 'PARSE URL HERE',
|
||||||
|
parseJSKey: 'parseJSKey here',
|
||||||
|
fileKey: 'parsefileKey here',
|
||||||
|
plantnetKey: 'plantnetKey here'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This file can be replaced during build by using the `fileReplacements` array.
|
||||||
|
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||||
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
|
export const environment = {
|
||||||
|
firebase: {
|
||||||
|
projectId: 'mobisapp-prod',
|
||||||
|
appId: 'firebase app id',
|
||||||
|
storageBucket: 'xx-prod.appspot.com',
|
||||||
|
//locationId: 'europe-west',
|
||||||
|
apiKey: 'xxx-no',
|
||||||
|
authDomain: 'xxxm',
|
||||||
|
messagingSenderId: 'xxx',
|
||||||
|
},
|
||||||
|
|
||||||
|
production: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For easier debugging in development mode, you can import the following file
|
||||||
|
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||||
|
*
|
||||||
|
* This import should be commented out in production mode because it will have a negative impact
|
||||||
|
* on performance if an error is thrown.
|
||||||
|
*/
|
||||||
|
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||||
@@ -4,12 +4,10 @@ import { RouteReuseStrategy } from '@angular/router';
|
|||||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
|
import { getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from './app.constant';
|
||||||
import { provideAuth, getAuth } from '@angular/fire/auth';
|
|
||||||
import { provideFirestore, getFirestore } from '@angular/fire/firestore';
|
import { provideFirestore, getFirestore } from '@angular/fire/firestore';
|
||||||
import { provideStorage, getStorage } from '@angular/fire/storage';
|
import { provideStorage, getStorage } from '@angular/fire/storage';
|
||||||
|
|
||||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
@@ -20,6 +18,14 @@ import { IonicStorageModule } from '@ionic/storage-angular';
|
|||||||
export function createTranslateLoader(http: HttpClient) {
|
export function createTranslateLoader(http: HttpClient) {
|
||||||
return new TranslateHttpLoader(http, './assets/languages/', '.json');
|
return new TranslateHttpLoader(http, './assets/languages/', '.json');
|
||||||
}
|
}
|
||||||
|
//imports for IOS fix
|
||||||
|
import {
|
||||||
|
getAuth,
|
||||||
|
indexedDBLocalPersistence,
|
||||||
|
initializeAuth,
|
||||||
|
provideAuth,
|
||||||
|
} from '@angular/fire/auth';
|
||||||
|
import { Capacitor } from '@capacitor/core';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@@ -31,7 +37,15 @@ export function createTranslateLoader(http: HttpClient) {
|
|||||||
IonicStorageModule.forRoot(),
|
IonicStorageModule.forRoot(),
|
||||||
TranslateModule.forRoot({loader: {provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient]}}),
|
TranslateModule.forRoot({loader: {provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient]}}),
|
||||||
provideFirebaseApp(() => initializeApp(environment.firebase)),
|
provideFirebaseApp(() => initializeApp(environment.firebase)),
|
||||||
provideAuth(() => getAuth()),
|
provideAuth(() => {
|
||||||
|
if (Capacitor.isNativePlatform()) {
|
||||||
|
return initializeAuth(getApp(), {
|
||||||
|
persistence: indexedDBLocalPersistence,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return getAuth();
|
||||||
|
}
|
||||||
|
}),
|
||||||
provideFirestore(() => getFirestore()),
|
provideFirestore(() => getFirestore()),
|
||||||
provideStorage(() => getStorage()),
|
provideStorage(() => getStorage()),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<ion-header class="ion-no-border">
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
|
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title class="ion-text-center">{{ "loginPage.title" | translate}}</ion-title>
|
<ion-title class="ion-text-center">{{ "loginPage.title" | translate}}</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export class LoginPage implements OnInit {
|
|||||||
await loading.present();
|
await loading.present();
|
||||||
this.authService
|
this.authService
|
||||||
.loginWithGoogle()
|
.loginWithGoogle()
|
||||||
.then(() => this.router.navigate(['/profile']))
|
.then(() => this.router.navigate(['/home']))
|
||||||
.catch((e) => console.log(e.message));
|
.catch((e) => console.log(e.message));
|
||||||
await loading.dismiss();
|
await loading.dismiss();
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ export class LoginPage implements OnInit {
|
|||||||
await loading.present();
|
await loading.present();
|
||||||
this.authService
|
this.authService
|
||||||
.loginWithApple()
|
.loginWithApple()
|
||||||
.then(() => this.router.navigate(['/profile']))
|
.then(() => this.router.navigate(['/home']))
|
||||||
.catch((e) => console.log(e.message));
|
.catch((e) => console.log(e.message));
|
||||||
await loading.dismiss();
|
await loading.dismiss();
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ export class LoginPage implements OnInit {
|
|||||||
await loading.present();
|
await loading.present();
|
||||||
this.authService
|
this.authService
|
||||||
.anonymousLogin()
|
.anonymousLogin()
|
||||||
.then(() => this.router.navigate(['/profile']))
|
.then(() => this.router.navigate(['/home']))
|
||||||
.catch((e) => console.log(e.message));
|
.catch((e) => console.log(e.message));
|
||||||
await loading.dismiss();
|
await loading.dismiss();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ion-header class="ion-no-border">
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title class="ion-text-center">{{ "profilePage.title" | translate }}</ion-title>
|
<ion-title class="ion-text-center">{{ "profilePage.title" | translate }}</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<ion-grid>
|
<ion-grid>
|
||||||
<ion-row>
|
<ion-row>
|
||||||
|
<ion-col>
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<ion-avatar (click)="changeImage()">
|
<ion-avatar (click)="changeImage()">
|
||||||
<img
|
<img
|
||||||
@@ -31,6 +32,12 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
</div>
|
</div>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<ion-button *ngIf="this.photoURL !== null" (click)="deleteAvatar()">Delete Avatar</ion-button>
|
||||||
|
<ion-button (click)="getWeather()" size="small">Get Weather</ion-button>
|
||||||
|
</ion-col>
|
||||||
|
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
<ion-grid *ngIf="!isAnonymous && user !== null">
|
<ion-grid *ngIf="!isAnonymous && user !== null">
|
||||||
@@ -100,11 +107,14 @@
|
|||||||
>{{ "profilePage.anonymous login text" | translate }}
|
>{{ "profilePage.anonymous login text" | translate }}
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
<ion-button (click)="logout()">{{ "profilePage.logout-button" | translate }}</ion-button>
|
||||||
|
<ion-button *ngIf="this.user !== null" (click)="deleteAccount()" color="danger"><ion-icon name="alert-circle-outline"></ion-icon>{{ "profilePage.delete-profile-button" | translate }}</ion-button>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<ion-content *ngIf="user === null">
|
<ion-content *ngIf="user === null">
|
||||||
<ion-card>
|
<ion-card>
|
||||||
<ion-card-header>{{ "profilePage.logged out" | translate }}</ion-card-header>
|
<ion-card-header>{{ "profilePage.logged out" | translate }}</ion-card-header>
|
||||||
<ion-card-content>Please <a routerLink="['/login']">login</a> to view your profile</ion-card-content>
|
<ion-card-content>Please <a routerLink="/login">login</a> to view your profile</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ ion-avatar {
|
|||||||
.preview {
|
.preview {
|
||||||
//margin-top: 50px;
|
//margin-top: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fallback {
|
.fallback {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { user, Auth } from '@angular/fire/auth';
|
|||||||
import { AvatarService } from '../../services/avatar.service';
|
import { AvatarService } from '../../services/avatar.service';
|
||||||
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
|
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { WeatherService } from 'src/app/services/weather.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-profile',
|
selector: 'app-profile',
|
||||||
@@ -30,24 +31,15 @@ export class ProfilePage implements OnInit {
|
|||||||
private loadingController: LoadingController,
|
private loadingController: LoadingController,
|
||||||
private alertController: AlertController,
|
private alertController: AlertController,
|
||||||
private afAuth: Auth,
|
private afAuth: Auth,
|
||||||
private translateService: TranslateService
|
private translateService: TranslateService,
|
||||||
|
private weatherService: WeatherService
|
||||||
) {
|
) {
|
||||||
// not the nicest way, sort of duplicate to have a URL in the profile variable and also check later the user variable
|
|
||||||
this.avatarService.getUserProfile().subscribe((data) => {
|
|
||||||
if (data) {
|
|
||||||
this.profile = data;
|
|
||||||
if (this.profile.imageUrl !== null) {
|
|
||||||
this.photoURL = this.profile.imageUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//console.log('data' , data);
|
|
||||||
});
|
|
||||||
|
|
||||||
user(this.afAuth).subscribe((response) => {
|
user(this.afAuth).subscribe((response) => {
|
||||||
//fill the user to verify if someone is logged in
|
//fill the user to verify if someone is logged in
|
||||||
this.user = response;
|
this.user = response;
|
||||||
if (response !== null) {
|
if (response !== null) {
|
||||||
console.log(response);
|
//console.log(response);
|
||||||
this.displayName = response.displayName;
|
this.displayName = response.displayName;
|
||||||
this.email = response.email;
|
this.email = response.email;
|
||||||
this.photoURL = response.photoURL;
|
this.photoURL = response.photoURL;
|
||||||
@@ -64,6 +56,12 @@ export class ProfilePage implements OnInit {
|
|||||||
}
|
}
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
deleteAvatar(){
|
||||||
|
this.avatarService.removeImage(); //remove image from storage
|
||||||
|
this.photoURL = null; //remove image from profile page
|
||||||
|
this.authService.updatePhotoURL(''); //remove profile url from profile
|
||||||
|
}
|
||||||
|
|
||||||
editDisplayNameField() {
|
editDisplayNameField() {
|
||||||
this.editDisplayName = true;
|
this.editDisplayName = true;
|
||||||
}
|
}
|
||||||
@@ -94,6 +92,16 @@ export class ProfilePage implements OnInit {
|
|||||||
await this.authService.logout();
|
await this.authService.logout();
|
||||||
this.router.navigateByUrl('/home', { replaceUrl: true });
|
this.router.navigateByUrl('/home', { replaceUrl: true });
|
||||||
}
|
}
|
||||||
|
//delete user in firebase project, not actual google profile
|
||||||
|
deleteAccount(){
|
||||||
|
//todo: show pop up with question if user really wants to delete his account if clicked yes, delete account
|
||||||
|
this.authService.deleteAccount();
|
||||||
|
this.router.navigateByUrl('/home', { replaceUrl: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
getWeather() {
|
||||||
|
this.weatherService.showCurrentWeather();
|
||||||
|
}
|
||||||
|
|
||||||
async changeImage() {
|
async changeImage() {
|
||||||
const image = await Camera.getPhoto({
|
const image = await Camera.getPhoto({
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-title>{{ "resetpwPage.title" | translate }}</ion-title>
|
<ion-title>{{ "resetpwPage.title" | translate }}</ion-title>
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="/"></ion-back-button>
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ion-header class="ion-no-border">
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
<ion-back-button defaultHref="home" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title class="ion-text-center">{{ "signupPage.title" | translate }}</ion-title>
|
<ion-title class="ion-text-center">{{ "signupPage.title" | translate }}</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
|
|||||||
0
src/app/environments/environment.prod.ts
Normal file
0
src/app/environments/environment.prod.ts
Normal file
0
src/app/environments/environment.ts
Normal file
0
src/app/environments/environment.ts
Normal file
@@ -1,12 +1,26 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { HomePage } from './home.page';
|
import { HomePage } from './home.page';
|
||||||
|
import {redirectUnauthorizedTo, redirectLoggedInTo, canActivate } from '@angular/fire/auth-guard';
|
||||||
|
|
||||||
|
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']);
|
||||||
|
const redirectLoggedInToProfile = () => redirectLoggedInTo(['profile']);
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: HomePage,
|
component: HomePage,
|
||||||
}
|
},
|
||||||
|
{ path: 'login',
|
||||||
|
loadChildren: () => import('../auth/login/login.module').then( m => m.LoginPageModule)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
loadChildren: () => import('../auth/profile/profile.module').then( m => m.ProfilePageModule),
|
||||||
|
...canActivate(redirectUnauthorizedToLogin)
|
||||||
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { HomePage } from './home.page';
|
|||||||
import { HomePageRoutingModule } from './home-routing.module';
|
import { HomePageRoutingModule } from './home-routing.module';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@@ -1,56 +1,84 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-title>M O B I S</ion-title>
|
<ion-title mode="ios">M O B I S</ion-title>
|
||||||
|
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="end">
|
||||||
<ion-menu-button slot="start"></ion-menu-button>
|
<ion-menu-button slot="end"></ion-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>{{ "measure.welcome-to-mobis" | translate }}</ion-card-header>
|
||||||
|
<ion-card-content class="ion-no-padding">
|
||||||
|
<ion-list lines="none" class="ion-no-padding">
|
||||||
|
<ion-list-header>{{ "menu.account" | translate }}</ion-list-header>
|
||||||
|
|
||||||
|
<ion-item button routerLink="/login" [disabled]="user">
|
||||||
|
<ion-icon name="person-add-outline"></ion-icon>
|
||||||
|
<ion-label>{{ "menu.login-register" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button routerLink="/profile" [disabled]="!user">
|
||||||
|
<ion-icon name="person-circle-outline"></ion-icon>
|
||||||
|
<ion-label>{{ "menu.profile" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button (click)="logout()" [disabled]="!user">
|
||||||
|
<ion-icon name="log-out-outline"></ion-icon>
|
||||||
|
<ion-label>{{ "menu.signout" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>{{ "measure.measure" | translate }}</ion-card-header>
|
||||||
|
<ion-card-content class="ion-no-padding">
|
||||||
|
<ion-list lines="none" class="ion-no-padding">
|
||||||
|
|
||||||
|
<ion-list-header>
|
||||||
|
{{ "measure.environment" | translate }}
|
||||||
|
</ion-list-header>
|
||||||
|
|
||||||
|
|
||||||
|
<ion-item button routerLink="/envpic" [disabled]="!user">
|
||||||
|
<ion-label>{{ "measure.envpic" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button routerLink="/ispex" [disabled]="!user">
|
||||||
|
<ion-label>{{ "measure.ispex2-observation" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-list-header>
|
||||||
|
{{ "measure.water-quality" | translate }}
|
||||||
|
</ion-list-header>
|
||||||
|
<ion-item button routerLink="/minisecchi" [disabled]="!user">
|
||||||
|
<ion-label> {{ "measure.mini-secchi-observation" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-list-header>
|
||||||
|
{{ "measure.air-quality" | translate }}
|
||||||
<ion-card>
|
</ion-list-header>
|
||||||
<ion-card-title>{{ "measure.welcome-to-mobis" | translate }}</ion-card-title>
|
<ion-item button routerLink="/canairiopm25" [disabled]="!user">
|
||||||
<ion-card-header>{{ "measure.measure" | translate }}</ion-card-header>
|
<ion-label> {{ "measure.canairiopm2.5-observation" | translate }}</ion-label>
|
||||||
<ion-card-content>
|
</ion-item>
|
||||||
<ion-list>
|
|
||||||
<ion-list-header>
|
|
||||||
{{ "measure.water-quality" | translate }}
|
|
||||||
</ion-list-header>
|
|
||||||
<ion-button shape="round" expand="block" routerLink="/minisecchi" strong="true" routerDirection="forward" >
|
|
||||||
{{ "measure.new-mini-secchi-observation" | translate }} </ion-button>
|
|
||||||
|
|
||||||
<ion-button shape="round" expand="block" routerLink="/ispex" routerDirection="forward" strong="true" routerDirection="forward" >
|
|
||||||
{{ "measure.new-ispex2-observation" | translate }} </ion-button>
|
|
||||||
|
|
||||||
<ion-list-header>
|
|
||||||
{{ "measure.air-quality" | translate }}
|
|
||||||
</ion-list-header>
|
|
||||||
<ion-button shape="round" expand="block" routerLink="/canairiopm25" routerDirection="forward" strong="true" routerDirection="forward" >
|
|
||||||
{{ "measure.new-canairiopm25-observation" | translate }}
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ion-button shape="round" expand="block" routerLink="/canairioco2" routerDirection="forward" strong="true" routerDirection="forward" >
|
|
||||||
{{ "measure.new-canairioco2-observation" | translate }}
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-list-header>
|
|
||||||
{{ "measure.biodiversity" | translate }}
|
|
||||||
</ion-list-header>
|
|
||||||
<ion-button shape="round" expand="block" routerLink="/plantnet" routerDirection="forward" strong="true" routerDirection="forward" >
|
|
||||||
{{ "measure.new-plantnet-observation" | translate }}
|
|
||||||
</ion-button>
|
|
||||||
</ion-list>
|
|
||||||
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ion-item button routerLink="/canairioco2" [disabled]="!user">
|
||||||
|
<ion-label>{{ "measure.canairioco2-observation" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-list-header>
|
||||||
|
{{ "measure.biodiversity" | translate }}
|
||||||
|
</ion-list-header>
|
||||||
|
|
||||||
|
<ion-item button routerLink="/plantnet" [disabled]="!user">
|
||||||
|
<ion-label> {{ "measure.plantnet-observation" | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,128 +1,7 @@
|
|||||||
#container {
|
ion-title {
|
||||||
text-align: center;
|
font-size: 1.5em;
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container strong {
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container p {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 22px;
|
|
||||||
|
|
||||||
color: #8c8c8c;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-avatar {
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview {
|
|
||||||
margin-top: 50px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fallback {
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #bfbfbf;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-label.title{
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: Open Sans, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-col{
|
|
||||||
text-align: start;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-col ion-button{
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-icon {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-card{
|
|
||||||
padding: 0px;
|
|
||||||
margin: 2px;
|
|
||||||
|
|
||||||
ion-card-header{
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-card-content{
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-header ion-img{
|
|
||||||
width:50px;
|
|
||||||
}
|
|
||||||
ion-img{
|
|
||||||
// display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-col {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-footer{
|
|
||||||
padding-top: 2px;
|
|
||||||
color: black;
|
|
||||||
display: flex;
|
|
||||||
align-items:center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border-top: 5px, solid;
|
|
||||||
border-color: #8c8c8c;
|
|
||||||
background-color: rgb(255, 255, 255);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-verified {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 20px;
|
|
||||||
color: #1a7900;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-not-verified {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 20px;
|
|
||||||
color: #790000;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text{
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { MenuController } from '@ionic/angular';
|
import { MenuController } from '@ionic/angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Geolocation, PositionOptions } from '@capacitor/geolocation';
|
import { Geolocation, PositionOptions } from '@capacitor/geolocation';
|
||||||
|
import { AuthService } from '../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
|
import { Storage } from '@ionic/storage-angular';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@@ -11,11 +14,23 @@ import { Geolocation, PositionOptions } from '@capacitor/geolocation';
|
|||||||
})
|
})
|
||||||
export class HomePage implements OnInit{
|
export class HomePage implements OnInit{
|
||||||
|
|
||||||
|
user = null;
|
||||||
|
language= '';
|
||||||
constructor(
|
constructor(
|
||||||
private menuController: MenuController, private translateService: TranslateService
|
private menuController: MenuController,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth,
|
||||||
|
private storage: Storage,
|
||||||
|
private router: Router
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
|
this.user = response;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +50,23 @@ export class HomePage implements OnInit{
|
|||||||
console.log('Current location: ', location);
|
console.log('Current location: ', location);
|
||||||
|
|
||||||
|
|
||||||
|
this.storage.create();
|
||||||
|
this.translateService.setDefaultLang('en'); //set default language English
|
||||||
|
|
||||||
|
this.language = await this.storage.get('language');
|
||||||
|
if (this.language === null){
|
||||||
|
console.log('no value for language in storage, trying getting from browser');
|
||||||
|
this.language = this.translateService.getBrowserLang();
|
||||||
|
|
||||||
|
}
|
||||||
|
this.translateService.use(this.language);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
await this.authService.logout();
|
||||||
|
this.router.navigateByUrl('/home', { replaceUrl: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
openMenu(){
|
openMenu(){
|
||||||
|
|||||||
@@ -1,82 +1,56 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>New Canair.io CO2 observation</ion-title>
|
|
||||||
|
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button></ion-back-button>
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
|
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
<ion-title>CANAIR.IO CO2</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
</ion-toolbar>
|
<ion-content>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label> Canairio output </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-header>
|
<ion-item>
|
||||||
|
<ion-label primary>CO2: {{CO2}} ({{CO2Interpret}}) </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-content>
|
<ion-item>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label>
|
|
||||||
Canairio output
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>CO2: {{CO2}} ({{CO2Interpret}}) </ion-label>
|
|
||||||
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>Temperature: {{TEMP}} C</ion-label>
|
<ion-label primary>Temperature: {{TEMP}} C</ion-label>
|
||||||
|
</ion-item>
|
||||||
</ion-item>
|
<ion-item>
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>Humidity: {{HUMID }} % </ion-label>
|
<ion-label primary>Humidity: {{HUMID }} % </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-item>
|
<ion-item>
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>PAX: {{PAX}} </ion-label>
|
<ion-label primary>PAX: {{PAX}} </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-item>
|
<ion-item>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>Lat: {{latitude}} </ion-label>
|
<ion-label primary>Lat: {{latitude}} </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-item>
|
<ion-item>
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
<ion-label primary>Lon: {{longitude}} </ion-label>
|
<ion-label primary>Lon: {{longitude}} </ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
<ion-label primary>Alt: {{altitude}} </ion-label>
|
<ion-label primary>Alt: {{altitude}} </ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-button (click)="startLogging()" [disabled]="isLogging"> Start logging</ion-button>
|
||||||
|
<ion-button (click)="stopLogging()" [disabled]="!isLogging"> Stop logging</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Log Interval:</ion-label>
|
||||||
|
<ion-input>{{logInterval}}</ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Log status:{{lblLogstatus}}</ion-label>
|
||||||
<ion-item>
|
</ion-item>
|
||||||
<ion-button (click) ="startLogging()">Start logging</ion-button>
|
</ion-content>
|
||||||
<ion-button (click) ="stopLogging()"> Stop logging</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-input>Log Interval:{{logInterval}}</ion-input>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label>Log status:{{lblLogstatus}}</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
</ion-content>
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/member-ordering */
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ENV } from '../../app.constant';
|
import { ENV } from '../../app.constant';
|
||||||
import { Geolocation} from '@capacitor/geolocation';
|
import { Geolocation } from '@capacitor/geolocation';
|
||||||
import { Guid } from "guid-typescript";
|
import { Guid } from 'guid-typescript';
|
||||||
import {interval, Subscription} from 'rxjs';
|
import { interval, Subscription } from 'rxjs';
|
||||||
import { Storage } from '@ionic/storage-angular';
|
import { Storage } from '@ionic/storage-angular';
|
||||||
import * as Parse from 'parse';
|
import * as Parse from 'parse';
|
||||||
import { BackgroundMode } from '@awesome-cordova-plugins/background-mode/ngx';
|
import { BackgroundMode } from '@awesome-cordova-plugins/background-mode/ngx';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
// Setup Bluetooth LE
|
// Setup Bluetooth LE
|
||||||
// Import the wrapper class directly
|
// Import the wrapper class directly
|
||||||
|
|
||||||
import { BleClient, numbersToDataView, numberToUUID, dataViewToText } from '@capacitor-community/bluetooth-le';
|
import {
|
||||||
|
BleClient,
|
||||||
|
numbersToDataView,
|
||||||
|
numberToUUID,
|
||||||
|
dataViewToText,
|
||||||
|
} from '@capacitor-community/bluetooth-le';
|
||||||
|
|
||||||
const PM25_SERVICE = 'C8D1D262-861F-4082-947E-F383A259AAF3';
|
const PM25_SERVICE = 'C8D1D262-861F-4082-947E-F383A259AAF3';
|
||||||
|
|
||||||
const PM25_SERVICE_LCASE= 'c8d1d262-861f-4082-947e-f383a259aaf3';
|
const PM25_SERVICE_LCASE = 'c8d1d262-861f-4082-947e-f383a259aaf3';
|
||||||
|
|
||||||
|
|
||||||
const PM25_SERVICE_CHARACTERISTIC = 'B0F332A8-A5AA-4F3F-BB43-F99E7791AE01';
|
const PM25_SERVICE_CHARACTERISTIC = 'B0F332A8-A5AA-4F3F-BB43-F99E7791AE01';
|
||||||
/* other services "B0F332A8-A5AA-4F3F-BB43-F99E7791AE02",
|
/* other services "B0F332A8-A5AA-4F3F-BB43-F99E7791AE02",
|
||||||
@@ -30,216 +37,219 @@ const PM25_SERVICE_CHARACTERISTIC = 'B0F332A8-A5AA-4F3F-BB43-F99E7791AE01';
|
|||||||
styleUrls: ['./canairioco2.page.scss'],
|
styleUrls: ['./canairioco2.page.scss'],
|
||||||
})
|
})
|
||||||
export class Canairioco2Page implements OnInit {
|
export class Canairioco2Page implements OnInit {
|
||||||
public txtco2: string;
|
user = null;
|
||||||
public datetime_ux: string;
|
language = '';
|
||||||
public datetime: Date;
|
|
||||||
public latitude: number;
|
|
||||||
public longitude: number;
|
|
||||||
public altitude: number;
|
|
||||||
private parseAppId: string = ENV.parseAppId;
|
|
||||||
private parseServerUrl: string = ENV.parseServerUrl;
|
|
||||||
private parseJSKey: string=ENV.parseJSKey;
|
|
||||||
public result: string;
|
|
||||||
public output_json: string;
|
|
||||||
public rec_uid: string;
|
|
||||||
public session_uid: string;
|
|
||||||
|
|
||||||
public deviceId:string;
|
public txtco2: string;
|
||||||
public intervalID:NodeJS.Timeout;
|
public datetime_ux: string;
|
||||||
public logInterval:number;
|
public datetime: Date;
|
||||||
public lblLogstatus: string;
|
public latitude: number;
|
||||||
public allData: any;
|
public longitude: number;
|
||||||
public CO2: string;
|
public altitude: number;
|
||||||
public TEMP: string;
|
private parseAppId: string = ENV.parseAppId;
|
||||||
public HUMID: string;
|
private parseServerUrl: string = ENV.parseServerUrl;
|
||||||
public PAX: string;
|
private parseJSKey: string = ENV.parseJSKey;
|
||||||
public CO2Interpret:string;
|
public result: string;
|
||||||
|
public output_json: string;
|
||||||
|
public rec_uid: string;
|
||||||
|
public session_uid: string;
|
||||||
|
|
||||||
public ParseServerKey:string;
|
public deviceId: string;
|
||||||
public ParseServerURL:string;
|
public intervalID: NodeJS.Timeout;
|
||||||
public ParseServerAppID:string;
|
public logInterval: number;
|
||||||
|
public lblLogstatus: string;
|
||||||
|
public allData: any;
|
||||||
|
public CO2: string;
|
||||||
|
public TEMP: string;
|
||||||
|
public HUMID: string;
|
||||||
|
public PAX: string;
|
||||||
|
public CO2Interpret: string;
|
||||||
|
|
||||||
|
public ParseServerKey: string;
|
||||||
|
public ParseServerURL: string;
|
||||||
|
public ParseServerAppID: string;
|
||||||
|
|
||||||
constructor(private backgroundMode: BackgroundMode) { }
|
isLogging = false;
|
||||||
|
|
||||||
|
|
||||||
// Initialize Parse SDK and connect to Parse Server
|
|
||||||
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
|
|
||||||
this.parseInitialize();
|
|
||||||
this.getLocation();
|
|
||||||
this.connect();
|
|
||||||
this.logInterval=5000;
|
|
||||||
this.lblLogstatus="Not logging";
|
|
||||||
this.readandSave();
|
|
||||||
|
|
||||||
this.parseAppId=ENV.parseAppId;
|
|
||||||
this.parseServerUrl=ENV.parseServerUrl;
|
|
||||||
this.parseJSKey=ENV.parseJSKey;
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private backgroundMode: BackgroundMode,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth,
|
||||||
|
private storage: Storage
|
||||||
|
) {
|
||||||
|
user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
|
this.user = response;
|
||||||
|
console.log(this.user.uid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// i am not sure if this is needed, because CO2 is not moving
|
// Initialize Parse SDK and connect to Parse Server
|
||||||
|
|
||||||
async startLogging() {
|
ngOnInit() {
|
||||||
|
this.parseInitialize();
|
||||||
|
this.getLocation();
|
||||||
|
this.connect();
|
||||||
|
this.logInterval = 5000;
|
||||||
|
this.lblLogstatus = 'Not logging';
|
||||||
|
|
||||||
this.backgroundMode.enable();
|
this.parseAppId = ENV.parseAppId;
|
||||||
|
this.parseServerUrl = ENV.parseServerUrl;
|
||||||
|
this.parseJSKey = ENV.parseJSKey;
|
||||||
|
|
||||||
this.lblLogstatus="Logging..";
|
this.readandSave();
|
||||||
// set session uid
|
}
|
||||||
this.session_uid = Guid.raw(); // make it a string
|
|
||||||
var str_counter;
|
|
||||||
str_counter=0;
|
|
||||||
|
|
||||||
|
// i am not sure if this is needed, because CO2 is not moving
|
||||||
|
|
||||||
this.intervalID = setInterval( () => {
|
async startLogging() {
|
||||||
|
this.backgroundMode.enable();
|
||||||
|
|
||||||
|
this.lblLogstatus = 'Logging..';
|
||||||
|
// set session uid
|
||||||
|
this.session_uid = Guid.raw(); // make it a string
|
||||||
|
var str_counter;
|
||||||
|
str_counter = 0;
|
||||||
|
|
||||||
this.lblLogstatus="Logging: " + str_counter.toString() ;
|
this.intervalID = setInterval(() => {
|
||||||
|
this.lblLogstatus = 'Logging: ' + str_counter.toString();
|
||||||
|
this.isLogging = true;
|
||||||
|
str_counter++;
|
||||||
|
|
||||||
str_counter++;
|
this.getLocation();
|
||||||
|
this.readandSave();
|
||||||
|
}, this.logInterval);
|
||||||
|
}
|
||||||
|
|
||||||
this.getLocation();
|
async stopLogging() {
|
||||||
this.readandSave();
|
clearInterval(this.intervalID);
|
||||||
|
this.lblLogstatus = 'Not logging';
|
||||||
|
this.isLogging = false;
|
||||||
|
|
||||||
},this.logInterval);
|
this.backgroundMode.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
async readandSave() {
|
||||||
|
const result = await BleClient.read(
|
||||||
|
this.deviceId,
|
||||||
|
PM25_SERVICE,
|
||||||
|
PM25_SERVICE_CHARACTERISTIC
|
||||||
|
);
|
||||||
|
console.log('canair.io result array', dataViewToText(result));
|
||||||
|
|
||||||
|
this.allData = JSON.parse(dataViewToText(result)); // parse json data and pass json string
|
||||||
|
|
||||||
}
|
this.CO2 = this.allData['CO2'];
|
||||||
|
|
||||||
|
// Indoor CO2 levels
|
||||||
|
if (Number(this.CO2) > 0 && Number(this.CO2) < 700)
|
||||||
|
this.CO2Interpret = 'Excellent';
|
||||||
|
if (Number(this.CO2) >= 700 && Number(this.CO2) < 800)
|
||||||
|
this.CO2Interpret = 'Good';
|
||||||
|
if (Number(this.CO2) >= 800 && Number(this.CO2) < 1000)
|
||||||
|
this.CO2Interpret = 'Fair';
|
||||||
|
if (Number(this.CO2) >= 1000 && Number(this.CO2) < 1500)
|
||||||
|
this.CO2Interpret = 'Mediocre';
|
||||||
|
if (Number(this.CO2) >= 1500) this.CO2Interpret = 'Bad';
|
||||||
|
|
||||||
async stopLogging() {
|
this.TEMP = this.allData['tmp'];
|
||||||
clearInterval(this.intervalID);
|
this.HUMID = this.allData['hum'];
|
||||||
this.lblLogstatus="Not logging";
|
this.PAX = this.allData['PAX'];
|
||||||
|
this.CO2 = this.allData['CO2'];
|
||||||
this.backgroundMode.disable();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async readandSave() {
|
|
||||||
|
|
||||||
const result = await BleClient.read(this.deviceId, PM25_SERVICE, PM25_SERVICE_CHARACTERISTIC);
|
|
||||||
console.log('canair.io result array', dataViewToText(result));
|
|
||||||
|
|
||||||
this.allData = JSON.parse(dataViewToText(result)); // parse json data and pass json string
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.CO2= this.allData['CO2'];
|
|
||||||
|
|
||||||
|
|
||||||
// Indoor CO2 levels
|
|
||||||
if (Number(this.CO2)>0 && Number(this.CO2)<700 ) this.CO2Interpret= ("Excellent");
|
|
||||||
if (Number(this.CO2)>=700 && Number(this.CO2)<800 )this.CO2Interpret= ("Good");
|
|
||||||
if (Number(this.CO2)>=800 && Number(this.CO2)<1000 )this.CO2Interpret= ("Fair");
|
|
||||||
if (Number(this.CO2)>=1000 && Number(this.CO2)<1500 )this.CO2Interpret= ("Mediocre");
|
|
||||||
if (Number(this.CO2)>=1500 ) this.CO2Interpret= ("Bad");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.TEMP= this.allData['tmp'];
|
|
||||||
this.HUMID= this.allData['hum'];
|
|
||||||
this.PAX= this.allData['PAX'];
|
|
||||||
this.CO2= this.allData['CO2'];
|
|
||||||
|
|
||||||
// lets assume we have
|
|
||||||
if (Number(this.CO2)>0) {
|
|
||||||
this.TEMP= this.allData['CO2T'];
|
|
||||||
this.HUMID= this.allData['CO2H'];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// this.txtco2= this.allData['CO2']; // this is the CO2 value
|
|
||||||
|
|
||||||
let d = new Date();
|
|
||||||
this.datetime=d;
|
|
||||||
var unixTimeStamp = Math.floor(d.getTime() / 1000);
|
|
||||||
this.datetime_ux=unixTimeStamp.toString();
|
|
||||||
this.output_json=dataViewToText(result);
|
|
||||||
this.rec_uid = Guid.raw(); // make it a string
|
|
||||||
|
|
||||||
var Comment = Parse.Object.extend('canairico2_raw_data');
|
|
||||||
var canairio_store = new Comment();
|
|
||||||
// set initial data record
|
|
||||||
canairio_store.set('session_UID',this.session_uid);
|
|
||||||
canairio_store.set('record_UID',this.rec_uid);
|
|
||||||
canairio_store.set('device_UID',this.deviceId);
|
|
||||||
canairio_store.set('output_json',dataViewToText(result));
|
|
||||||
canairio_store.set('TEMP',this.TEMP);
|
|
||||||
canairio_store.set('HUMID',this.HUMID);
|
|
||||||
canairio_store.set('CO2',this.CO2);
|
|
||||||
canairio_store.set('PAX',this.PAX);
|
|
||||||
canairio_store.set('latitude',this.latitude);
|
|
||||||
canairio_store.set('longitude',this.longitude);
|
|
||||||
canairio_store.set('altitude',this.altitude);
|
|
||||||
canairio_store.set('datetime_ux',this.datetime_ux);
|
|
||||||
await canairio_store.save();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get location and save to class variables
|
|
||||||
|
|
||||||
async getLocation() {
|
|
||||||
const position = await Geolocation.getCurrentPosition({enableHighAccuracy: true});
|
|
||||||
this.latitude = position.coords.latitude;
|
|
||||||
console.log (position.coords.latitude);
|
|
||||||
this.longitude = position.coords.longitude;
|
|
||||||
this.altitude = position.coords.altitude;
|
|
||||||
return position.coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect to parse server and initialize
|
|
||||||
|
|
||||||
private parseInitialize() {
|
|
||||||
|
|
||||||
Parse.initialize(this.parseAppId, this.parseJSKey);
|
|
||||||
|
|
||||||
(Parse as any).serverURL = this.ParseServerURL; // use your server url
|
|
||||||
|
|
||||||
|
// lets assume we have
|
||||||
|
if (Number(this.CO2) > 0) {
|
||||||
|
this.TEMP = this.allData['CO2T'];
|
||||||
|
this.HUMID = this.allData['CO2H'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this.txtco2= this.allData['CO2']; // this is the CO2 value
|
||||||
|
|
||||||
async connect(){
|
const d = new Date();
|
||||||
|
this.datetime = d;
|
||||||
|
const unixTimeStamp = Math.floor(d.getTime() / 1000);
|
||||||
|
this.datetime_ux = unixTimeStamp.toString();
|
||||||
|
this.output_json = dataViewToText(result);
|
||||||
|
this.rec_uid = Guid.raw(); // make it a string\
|
||||||
|
|
||||||
try {
|
// The parse part
|
||||||
await BleClient.initialize();
|
// create parse class
|
||||||
const device = await BleClient.requestDevice({
|
const canairio_data_store = Parse.Object.extend('canairio_raw_data');
|
||||||
namePrefix: 'CanAirIO',
|
|
||||||
optionalServices : [PM25_SERVICE_LCASE]
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log('device', device);
|
// create new instance of parse class
|
||||||
|
const canairio_store = new canairio_data_store();
|
||||||
|
|
||||||
await BleClient.connect(device.deviceId);
|
// set value for parse clas
|
||||||
console.log('connected to device', device);
|
|
||||||
|
|
||||||
this.deviceId=device.deviceId;
|
// set initial data record
|
||||||
this.readandSave();
|
canairio_store.set('session_UID', this.session_uid);
|
||||||
|
canairio_store.set('record_UID', this.rec_uid);
|
||||||
|
canairio_store.set('device_UID', this.deviceId);
|
||||||
|
canairio_store.set('output_json', dataViewToText(result));
|
||||||
|
canairio_store.set('TEMP', this.TEMP);
|
||||||
|
canairio_store.set('HUMID', this.HUMID);
|
||||||
|
canairio_store.set('CO2', this.CO2);
|
||||||
|
canairio_store.set('PAX', this.PAX);
|
||||||
|
canairio_store.set('latitude', this.latitude);
|
||||||
|
canairio_store.set('longitude', this.longitude);
|
||||||
|
canairio_store.set('altitude', this.altitude);
|
||||||
|
|
||||||
|
canairio_store.set('datetime_ux', this.datetime_ux);
|
||||||
setTimeout(async () => {
|
canairio_store.set('user_uid', this.user.uid);
|
||||||
await BleClient.stopLEScan();
|
canairio_store.set('name', this.user.displayName);
|
||||||
console.log('stopped scanning');
|
canairio_store.set('email', this.user.email);
|
||||||
}, 5000);
|
//log the parse save to console
|
||||||
} catch (error) {
|
canairio_store.save().then(
|
||||||
console.error(error);
|
(saveResult: any) => {
|
||||||
|
console.log(saveResult);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get location and save to class variables
|
||||||
|
async getLocation() {
|
||||||
|
const position = await Geolocation.getCurrentPosition({
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
});
|
||||||
|
this.latitude = position.coords.latitude;
|
||||||
|
console.log(position.coords.latitude);
|
||||||
|
this.longitude = position.coords.longitude;
|
||||||
|
this.altitude = position.coords.altitude;
|
||||||
|
return position.coords;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
// connect to parse server and initialize
|
||||||
|
private parseInitialize() {
|
||||||
|
Parse.initialize(ENV.parseAppId, ENV.parseJSKey);
|
||||||
|
(Parse as any).serverURL = ENV.parseServerUrl; // use your server url
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
try {
|
||||||
|
await BleClient.initialize();
|
||||||
|
const device = await BleClient.requestDevice({
|
||||||
|
namePrefix: 'CanAirIO',
|
||||||
|
optionalServices: [PM25_SERVICE_LCASE],
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log('device', device);
|
||||||
|
|
||||||
|
await BleClient.connect(device.deviceId);
|
||||||
|
console.log('connected to device', device);
|
||||||
|
|
||||||
|
this.deviceId = device.deviceId;
|
||||||
|
this.readandSave();
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await BleClient.stopLEScan();
|
||||||
|
console.log('stopped scanning');
|
||||||
|
}, 5000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +1,59 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>New Canair.io PM2.5 observation</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>CANAIR.IO PM2.5</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label> Canairio output </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label primary>{{txtpm25}} </ion-label>
|
||||||
<ion-item>
|
|
||||||
<ion-label>
|
|
||||||
Canairio output
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>{{txtpm25}} </ion-label>
|
|
||||||
|
|
||||||
</ion-item>
|
<ion-item>
|
||||||
|
<ion-label primary>PM 2.5: {{PM25}} ({{PM25Interpret}}) </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label primary>PM 2.5: {{PM25}} ({{PM25Interpret}}) </ion-label>
|
<ion-label primary>Temperature: {{TEMP}} C</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label primary>Humidity: {{HUMID }} % </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-item>
|
<ion-item>
|
||||||
|
<ion-label primary>PAX: {{PAX}} </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label primary>Lat: {{latitude}} </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label primary>Lon: {{longitude}} </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label primary>Temperature: {{TEMP}} C</ion-label>
|
<ion-label primary>Alt: {{altitude}} </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-item>
|
<ion-item>
|
||||||
<ion-item>
|
<ion-button (click)="startLogging()" [disabled]="isLogging">Start logging</ion-button>
|
||||||
<ion-label primary>Humidity: {{HUMID }} % </ion-label>
|
<ion-button (click)="stopLogging()" [disabled]="!isLogging"> Stop logging</ion-button>
|
||||||
|
</ion-item>
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>PAX: {{PAX}} </ion-label>
|
|
||||||
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label primary>Lat: {{latitude}} </ion-label>
|
|
||||||
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
<ion-label primary>Lon: {{longitude}} </ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
<ion-label primary>Alt: {{altitude}} </ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-button (click) ="startLogging()">Start logging</ion-button>
|
|
||||||
<ion-button (click) ="stopLogging()"> Stop logging</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-input>Log Interval:{{logInterval}}</ion-input>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label>Log status:{{lblLogstatus}}</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Log Interval:</ion-label>
|
||||||
|
<ion-input>{{logInterval}}</ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Log status:{{lblLogstatus}}</ion-label>
|
||||||
|
</ion-item>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/member-ordering */
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ENV } from '../../app.constant';
|
import { ENV } from '../../app.constant';
|
||||||
import { Geolocation} from '@capacitor/geolocation';
|
import { Geolocation } from '@capacitor/geolocation';
|
||||||
import { Guid } from "guid-typescript";
|
import { Guid } from 'guid-typescript';
|
||||||
import {interval, Subscription} from 'rxjs';
|
import { interval, Subscription } from 'rxjs';
|
||||||
import { Storage } from '@ionic/storage-angular';
|
import { Storage } from '@ionic/storage-angular';
|
||||||
import * as Parse from 'parse';
|
import * as Parse from 'parse';
|
||||||
import { BackgroundMode } from '@awesome-cordova-plugins/background-mode/ngx';
|
import { BackgroundMode } from '@awesome-cordova-plugins/background-mode/ngx';
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
// Import the wrapper class directly
|
// Import the wrapper class directly
|
||||||
|
|
||||||
import { BleClient, numbersToDataView, numberToUUID, dataViewToText } from '@capacitor-community/bluetooth-le';
|
import {
|
||||||
|
BleClient,
|
||||||
|
numbersToDataView,
|
||||||
|
numberToUUID,
|
||||||
|
dataViewToText,
|
||||||
|
} from '@capacitor-community/bluetooth-le';
|
||||||
|
|
||||||
const PM25_SERVICE = 'C8D1D262-861F-4082-947E-F383A259AAF3';
|
const PM25_SERVICE = 'C8D1D262-861F-4082-947E-F383A259AAF3';
|
||||||
|
|
||||||
const PM25_SERVICE_LCASE= 'c8d1d262-861f-4082-947e-f383a259aaf3';
|
const PM25_SERVICE_LCASE = 'c8d1d262-861f-4082-947e-f383a259aaf3';
|
||||||
|
|
||||||
|
|
||||||
const PM25_SERVICE_CHARACTERISTIC = 'B0F332A8-A5AA-4F3F-BB43-F99E7791AE01';
|
const PM25_SERVICE_CHARACTERISTIC = 'B0F332A8-A5AA-4F3F-BB43-F99E7791AE01';
|
||||||
/* other services "B0F332A8-A5AA-4F3F-BB43-F99E7791AE02",
|
/* other services "B0F332A8-A5AA-4F3F-BB43-F99E7791AE02",
|
||||||
@@ -30,222 +34,217 @@ const PM25_SERVICE_CHARACTERISTIC = 'B0F332A8-A5AA-4F3F-BB43-F99E7791AE01';
|
|||||||
styleUrls: ['./canairiopm25.page.scss'],
|
styleUrls: ['./canairiopm25.page.scss'],
|
||||||
})
|
})
|
||||||
export class Canairiopm25Page implements OnInit {
|
export class Canairiopm25Page implements OnInit {
|
||||||
public txtpm25: string;
|
image = '';
|
||||||
public datetime_ux: string;
|
user = null;
|
||||||
public datetime: Date;
|
public txtpm25: string;
|
||||||
public latitude: number;
|
public datetime_ux: string;
|
||||||
public longitude: number;
|
public datetime: Date;
|
||||||
public altitude: number;
|
public latitude: number;
|
||||||
private parseAppId: string = ENV.parseAppId;
|
public longitude: number;
|
||||||
private parseServerUrl: string = ENV.parseServerUrl;
|
public altitude: number;
|
||||||
private parseJSKey: string=ENV.parseJSKey;
|
private parseAppId: string = ENV.parseAppId;
|
||||||
public result: string;
|
private parseServerUrl: string = ENV.parseServerUrl;
|
||||||
public output_json: string;
|
private parseJSKey: string = ENV.parseJSKey;
|
||||||
public rec_uid: string;
|
public result: string;
|
||||||
public session_uid: string;
|
public output_json: string;
|
||||||
|
public rec_uid: string;
|
||||||
|
public session_uid: string;
|
||||||
|
|
||||||
public deviceId:string;
|
public deviceId: string;
|
||||||
public intervalID:NodeJS.Timeout;
|
public intervalID: NodeJS.Timeout;
|
||||||
public logInterval:number;
|
public logInterval: number;
|
||||||
public lblLogstatus: string;
|
public lblLogstatus: string;
|
||||||
public allData: any;
|
public allData: any;
|
||||||
public PM25: string;
|
public PM25: string;
|
||||||
public CO2: string;
|
public CO2: string;
|
||||||
public TEMP: string;
|
public TEMP: string;
|
||||||
public HUMID: string;
|
public HUMID: string;
|
||||||
public PAX: string;
|
public PAX: string;
|
||||||
public PM25Interpret:string;
|
public PM25Interpret: string;
|
||||||
|
|
||||||
public ParseServerKey:string;
|
|
||||||
public ParseServerURL:string;
|
|
||||||
public ParseServerAppID:string;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(private backgroundMode: BackgroundMode) { }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
|
|
||||||
this.parseInitialize();
|
|
||||||
this.getLocation();
|
|
||||||
this.connect();
|
|
||||||
this.logInterval=5000;
|
|
||||||
this.lblLogstatus="Not logging";
|
|
||||||
this.readandSave();
|
|
||||||
|
|
||||||
this.parseAppId=ENV.parseAppId;
|
|
||||||
this.parseServerUrl=ENV.parseServerUrl;
|
|
||||||
this.parseJSKey=ENV.parseJSKey;
|
|
||||||
|
|
||||||
|
public ParseServerKey: string;
|
||||||
|
public ParseServerURL: string;
|
||||||
|
public ParseServerAppID: string;
|
||||||
|
isLogging = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private backgroundMode: BackgroundMode,
|
||||||
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth
|
||||||
|
) {
|
||||||
|
user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
|
this.user = response;
|
||||||
|
console.log(this.user.uid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.parseInitialize();
|
||||||
|
this.getLocation();
|
||||||
|
this.connect();
|
||||||
|
this.logInterval = 5000;
|
||||||
|
this.lblLogstatus = 'Not logging';
|
||||||
|
this.readandSave();
|
||||||
|
|
||||||
|
this.parseAppId = ENV.parseAppId;
|
||||||
|
this.parseServerUrl = ENV.parseServerUrl;
|
||||||
|
this.parseJSKey = ENV.parseJSKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async startLogging() {
|
||||||
|
this.backgroundMode.enable();
|
||||||
|
|
||||||
|
this.lblLogstatus = 'Logging..';
|
||||||
|
this.isLogging = true;
|
||||||
|
// set session uid
|
||||||
|
this.session_uid = Guid.raw(); // make it a string
|
||||||
|
var str_counter;
|
||||||
|
str_counter = 0;
|
||||||
|
|
||||||
|
this.intervalID = setInterval(() => {
|
||||||
|
this.lblLogstatus = 'Logging: ' + str_counter.toString();
|
||||||
|
|
||||||
|
str_counter++;
|
||||||
|
|
||||||
|
this.getLocation();
|
||||||
|
this.readandSave();
|
||||||
|
}, this.logInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopLogging() {
|
||||||
|
clearInterval(this.intervalID);
|
||||||
|
this.lblLogstatus = 'Not logging';
|
||||||
|
this.isLogging = false;
|
||||||
|
|
||||||
|
this.backgroundMode.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
async readandSave() {
|
||||||
|
const result = await BleClient.read(
|
||||||
|
this.deviceId,
|
||||||
|
PM25_SERVICE,
|
||||||
|
PM25_SERVICE_CHARACTERISTIC
|
||||||
|
);
|
||||||
|
console.log('canair.io result array', dataViewToText(result));
|
||||||
|
|
||||||
|
this.allData = JSON.parse(dataViewToText(result)); // parse json data and pass json string
|
||||||
|
|
||||||
|
this.PM25 = this.allData['P25'];
|
||||||
|
|
||||||
|
//outdoor
|
||||||
|
if (Number(this.PM25) > 0 && Number(this.PM25) < 25)
|
||||||
|
{this.PM25Interpret = 'Good';}
|
||||||
|
if (Number(this.PM25) > 24 && Number(this.PM25) < 51)
|
||||||
|
{this.PM25Interpret = 'Fair';}
|
||||||
|
if (Number(this.PM25) > 49 && Number(this.PM25) < 101)
|
||||||
|
{this.PM25Interpret = 'Poor';}
|
||||||
|
if (Number(this.PM25) > 100 && Number(this.PM25) < 300)
|
||||||
|
{this.PM25Interpret = 'Very Poor';}
|
||||||
|
if (Number(this.PM25) > 300) {this.PM25Interpret = 'Extremely Poor';}
|
||||||
|
|
||||||
|
this.TEMP = this.allData['tmp'];
|
||||||
|
this.HUMID = this.allData['hum'];
|
||||||
|
this.PAX = this.allData['PAX'];
|
||||||
|
this.CO2 = this.allData['CO2'];
|
||||||
|
|
||||||
|
// lets assume we have
|
||||||
|
if (Number(this.CO2) > 0) {
|
||||||
|
this.TEMP = this.allData['CO2T'];
|
||||||
|
this.HUMID = this.allData['CO2H'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = new Date();
|
||||||
|
this.datetime = d;
|
||||||
|
const unixTimeStamp = Math.floor(d.getTime() / 1000);
|
||||||
|
this.datetime_ux = unixTimeStamp.toString();
|
||||||
|
this.output_json = dataViewToText(result);
|
||||||
|
this.txtpm25 = this.output_json;
|
||||||
|
this.rec_uid = Guid.raw(); // make it a string
|
||||||
|
|
||||||
|
// The parse part
|
||||||
|
// create parse class
|
||||||
|
|
||||||
|
const canairio_data_store = Parse.Object.extend('canairio_raw_data');
|
||||||
|
|
||||||
|
|
||||||
async startLogging() {
|
// create new instance of parse class
|
||||||
|
|
||||||
this.backgroundMode.enable();
|
const canairio_store = new canairio_data_store();
|
||||||
|
|
||||||
this.lblLogstatus="Logging..";
|
|
||||||
// set session uid
|
|
||||||
this.session_uid = Guid.raw(); // make it a string
|
|
||||||
var str_counter;
|
|
||||||
str_counter=0;
|
|
||||||
|
|
||||||
|
|
||||||
this.intervalID = setInterval( () => {
|
// set value for parse clas
|
||||||
|
|
||||||
|
|
||||||
this.lblLogstatus="Logging: " + str_counter.toString() ;
|
|
||||||
|
|
||||||
str_counter++;
|
|
||||||
|
|
||||||
this.getLocation();
|
|
||||||
this.readandSave();
|
|
||||||
|
|
||||||
},this.logInterval);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async stopLogging() {
|
|
||||||
clearInterval(this.intervalID);
|
|
||||||
this.lblLogstatus="Not logging";
|
|
||||||
|
|
||||||
this.backgroundMode.disable();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async readandSave() {
|
|
||||||
|
|
||||||
const result = await BleClient.read(this.deviceId, PM25_SERVICE, PM25_SERVICE_CHARACTERISTIC);
|
|
||||||
console.log('canair.io result array', dataViewToText(result));
|
|
||||||
|
|
||||||
this.allData = JSON.parse(dataViewToText(result)); // parse json data and pass json string
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.PM25= this.allData['P25'];
|
|
||||||
|
|
||||||
|
|
||||||
//outdoor
|
|
||||||
if (Number(this.PM25)>0 && Number(this.PM25)<25 ) this.PM25Interpret= ("Good");
|
|
||||||
if (Number(this.PM25)>24 && Number(this.PM25)<51 )this.PM25Interpret= ("Fair");
|
|
||||||
if (Number(this.PM25)>49 && Number(this.PM25)<101 )this.PM25Interpret= ("Poor");
|
|
||||||
if (Number(this.PM25)>100 && Number(this.PM25)<300 )this.PM25Interpret= ("Very Poor");
|
|
||||||
if (Number(this.PM25)>300 ) this.PM25Interpret= ("Extremely Poor");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.TEMP= this.allData['tmp'];
|
|
||||||
this.HUMID= this.allData['hum'];
|
|
||||||
this.PAX= this.allData['PAX'];
|
|
||||||
this.CO2= this.allData['CO2'];
|
|
||||||
|
|
||||||
// lets assume we have
|
|
||||||
if (Number(this.CO2)>0) {
|
|
||||||
this.TEMP= this.allData['CO2T'];
|
|
||||||
this.HUMID= this.allData['CO2H'];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let d = new Date();
|
|
||||||
this.datetime=d;
|
|
||||||
var unixTimeStamp = Math.floor(d.getTime() / 1000);
|
|
||||||
this.datetime_ux=unixTimeStamp.toString();
|
|
||||||
this.output_json=dataViewToText(result);
|
|
||||||
this.txtpm25=this.output_json;
|
|
||||||
this.rec_uid = Guid.raw(); // make it a string
|
|
||||||
|
|
||||||
var Comment = Parse.Object.extend('canairio_raw_data');
|
|
||||||
var canairio_store = new Comment();
|
|
||||||
// set initial data record
|
// set initial data record
|
||||||
canairio_store.set('session_UID',this.session_uid);
|
canairio_store.set('session_UID',this.session_uid);
|
||||||
canairio_store.set('record_UID',this.rec_uid);
|
canairio_store.set('record_UID',this.rec_uid);
|
||||||
canairio_store.set('device_UID',this.deviceId);
|
canairio_store.set('device_UID',this.deviceId);
|
||||||
canairio_store.set('output_json',dataViewToText(result));
|
canairio_store.set('output_json',dataViewToText(result));
|
||||||
canairio_store.set('PM25',this.PM25);
|
|
||||||
canairio_store.set('TEMP',this.TEMP);
|
canairio_store.set('TEMP',this.TEMP);
|
||||||
canairio_store.set('HUMID',this.HUMID);
|
canairio_store.set('HUMID',this.HUMID);
|
||||||
canairio_store.set('CO2',this.CO2);
|
canairio_store.set('CO2',this.CO2);
|
||||||
canairio_store.set('PAX',this.PAX);
|
canairio_store.set('PAX',this.PAX);
|
||||||
|
|
||||||
canairio_store.set('latitude',this.latitude);
|
canairio_store.set('latitude',this.latitude);
|
||||||
canairio_store.set('longitude',this.longitude);
|
canairio_store.set('longitude',this.longitude);
|
||||||
canairio_store.set('altitude',this.altitude);
|
canairio_store.set('altitude',this.altitude);
|
||||||
|
|
||||||
canairio_store.set('datetime_ux',this.datetime_ux);
|
canairio_store.set('datetime_ux',this.datetime_ux);
|
||||||
await canairio_store.save();
|
canairio_store.set('user_uid', this.user.uid);
|
||||||
|
canairio_store.set('name', this.user.displayName);
|
||||||
|
canairio_store.set('email', this.user.email);
|
||||||
|
//log the parse save to console
|
||||||
|
canairio_store.save().then((saveResult: any) => {
|
||||||
|
console.log(saveResult);
|
||||||
|
|
||||||
}
|
}, (error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLocation() {
|
||||||
|
const position = await Geolocation.getCurrentPosition({
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async getLocation() {
|
|
||||||
const position = await Geolocation.getCurrentPosition({enableHighAccuracy: true});
|
|
||||||
this.latitude = position.coords.latitude;
|
|
||||||
console.log (position.coords.latitude);
|
|
||||||
this.longitude = position.coords.longitude;
|
|
||||||
this.altitude = position.coords.altitude;
|
|
||||||
return position.coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private parseInitialize() {
|
|
||||||
|
|
||||||
Parse.initialize(this.parseAppId, this.parseJSKey);
|
|
||||||
|
|
||||||
(Parse as any).serverURL = this.ParseServerURL; // use your server url
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async connect(){
|
|
||||||
|
|
||||||
try {
|
|
||||||
await BleClient.initialize();
|
|
||||||
const device = await BleClient.requestDevice({
|
|
||||||
namePrefix: 'CanAirIO',
|
|
||||||
optionalServices : [PM25_SERVICE_LCASE]
|
|
||||||
});
|
});
|
||||||
|
this.latitude = position.coords.latitude;
|
||||||
// console.log('device', device);
|
console.log(position.coords.latitude);
|
||||||
|
this.longitude = position.coords.longitude;
|
||||||
await BleClient.connect(device.deviceId);
|
this.altitude = position.coords.altitude;
|
||||||
console.log('connected to device', device);
|
return position.coords;
|
||||||
|
|
||||||
this.deviceId=device.deviceId;
|
|
||||||
this.readandSave();
|
|
||||||
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
|
||||||
await BleClient.stopLEScan();
|
|
||||||
console.log('stopped scanning');
|
|
||||||
}, 5000);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
private parseInitialize() {
|
||||||
|
|
||||||
|
Parse.initialize(ENV.parseAppId, ENV.parseJSKey);
|
||||||
|
(Parse as any).serverURL = ENV.parseServerUrl; // use your server url
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
try {
|
||||||
|
await BleClient.initialize();
|
||||||
|
const device = await BleClient.requestDevice({
|
||||||
|
namePrefix: 'CanAirIO',
|
||||||
|
optionalServices: [PM25_SERVICE_LCASE],
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log('device', device);
|
||||||
|
|
||||||
|
await BleClient.connect(device.deviceId);
|
||||||
|
console.log('connected to device', device);
|
||||||
|
|
||||||
|
this.deviceId = device.deviceId;
|
||||||
|
this.readandSave();
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await BleClient.stopLEScan();
|
||||||
|
console.log('stopped scanning');
|
||||||
|
}, 5000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { ClassifyPage } from './classify.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ClassifyPage
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class ClassifyPageRoutingModule {}
|
||||||
20
src/app/mobisplugins/ispex/classify/classify.module.ts
Normal file
20
src/app/mobisplugins/ispex/classify/classify.module.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { ClassifyPageRoutingModule } from './classify-routing.module';
|
||||||
|
|
||||||
|
import { ClassifyPage } from './classify.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
IonicModule,
|
||||||
|
ClassifyPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [ClassifyPage]
|
||||||
|
})
|
||||||
|
export class ClassifyPageModule {}
|
||||||
55
src/app/mobisplugins/ispex/classify/classify.page.html
Normal file
55
src/app/mobisplugins/ispex/classify/classify.page.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Classify your spectrum</ion-title>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Choose a class</ion-label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ion-select>
|
||||||
|
<ion-select-option value="gray">
|
||||||
|
Gray card
|
||||||
|
</ion-select-option>
|
||||||
|
<ion-select-option value="sky">
|
||||||
|
Sky
|
||||||
|
</ion-select-option>
|
||||||
|
<ion-select-option value="water">
|
||||||
|
Water
|
||||||
|
</ion-select-option>
|
||||||
|
|
||||||
|
<ion-select-option value="lamp">
|
||||||
|
Lamp
|
||||||
|
</ion-select-option>
|
||||||
|
<ion-select-option value="other">
|
||||||
|
|
||||||
|
Other
|
||||||
|
</ion-select-option>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
|
||||||
|
</ion-content>
|
||||||
24
src/app/mobisplugins/ispex/classify/classify.page.spec.ts
Normal file
24
src/app/mobisplugins/ispex/classify/classify.page.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { ClassifyPage } from './classify.page';
|
||||||
|
|
||||||
|
describe('ClassifyPage', () => {
|
||||||
|
let component: ClassifyPage;
|
||||||
|
let fixture: ComponentFixture<ClassifyPage>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ClassifyPage ],
|
||||||
|
imports: [IonicModule.forRoot()]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ClassifyPage);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
15
src/app/mobisplugins/ispex/classify/classify.page.ts
Normal file
15
src/app/mobisplugins/ispex/classify/classify.page.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-classify',
|
||||||
|
templateUrl: './classify.page.html',
|
||||||
|
styleUrls: ['./classify.page.scss'],
|
||||||
|
})
|
||||||
|
export class ClassifyPage implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,10 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: IspexPage
|
component: IspexPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'classify',
|
||||||
|
loadChildren: () => import('./classify/classify.module').then( m => m.ClassifyPageModule)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { IonicModule } from '@ionic/angular';
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
import { IspexPageRoutingModule } from './ispex-routing.module';
|
import { IspexPageRoutingModule } from './ispex-routing.module';
|
||||||
import { PreviewPageModule } from "./preview/preview.module"
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { Vibration } from '@ionic-native/vibration/ngx';
|
|
||||||
|
|
||||||
import { IspexPage } from './ispex.page';
|
import { IspexPage } from './ispex.page';
|
||||||
|
import { PreviewPage } from './preview/preview.page';
|
||||||
|
import { PreviewPageModule } from "./preview/preview.module"
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
IonicModule,
|
IonicModule,
|
||||||
IspexPageRoutingModule
|
IspexPageRoutingModule,
|
||||||
|
TranslateModule,
|
||||||
|
PreviewPageModule
|
||||||
|
|
||||||
],
|
],
|
||||||
declarations: [IspexPage],
|
declarations: [IspexPage],
|
||||||
providers: [Vibration]
|
|
||||||
})
|
})
|
||||||
export class IspexPageModule {}
|
export class IspexPageModule {}
|
||||||
|
|||||||
@@ -1,64 +1,32 @@
|
|||||||
<ion-header>
|
<ion-header [translucent]="true">
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar>
|
||||||
<ion-title>New iSPEX observation</ion-title>
|
|
||||||
|
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-menu-button slot="start"></ion-menu-button>
|
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
|
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
Snap a spectrum
|
||||||
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content [fullscreen]="false">
|
||||||
|
<ion-card>
|
||||||
|
Connect your iSPEX unit to your phone and snap a spectrum.
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
<ion-content [fullscreen]="true">
|
|
||||||
<div *ngIf="image">
|
<div *ngIf="image">
|
||||||
<img #image id="img" [src]="image" alt="" srcset="">
|
<img [src]="image" alt="" srcset="">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="uploadSuccess">
|
||||||
|
<ion-button expand="block" routerLink="classify" routerDirection="forward">
|
||||||
|
Lets classify this spectrum!
|
||||||
|
</ion-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-button (click)="openCamera()" color="primary" expand="block" fill="solid" size="default">
|
<ion-button (click)="openCamera()" color="primary" expand="block" fill="solid" size="default">
|
||||||
Attach the iSPEX unit and open camera
|
Snap a spectrum
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
|
|
||||||
<ion-card>
|
|
||||||
<ion-card-title>Mount iSPEX2 unit on phone</ion-card-title>
|
|
||||||
<ion-card-subtitle></ion-card-subtitle>
|
|
||||||
<ion-card-content>1. If you have a smartphone
|
|
||||||
cover or case, remove it.<br>
|
|
||||||
2. Take your smartphone in
|
|
||||||
one hand and the iSPEX2
|
|
||||||
add-on in the other. If this
|
|
||||||
is not possible, use a table
|
|
||||||
or similar surface to lay the
|
|
||||||
smartphone on.<br>
|
|
||||||
<ion-img src="assets/images/ispex-mount1.png"></ion-img>
|
|
||||||
3. The iSPEX2 add-on will be mounted on the back side of the phone and the clip will be on the front screen.
|
|
||||||
Slide your smartphone into the smartphone clip. You may need to use your other
|
|
||||||
hand to slightly open the clip, especially for thicker
|
|
||||||
smartphones.<br>
|
|
||||||
<ion-img src="assets/images/ispex-mount3.png"></ion-img>
|
|
||||||
4. Slide the iSPEX2
|
|
||||||
add-on over your
|
|
||||||
smartphone until the
|
|
||||||
bars on the top and
|
|
||||||
left sides fit snugly.<br>
|
|
||||||
<ion-img src="assets/images/ispex-mount2.png"></ion-img>
|
|
||||||
5. Firmly press the suction cup on the
|
|
||||||
back of the smartphone until it is
|
|
||||||
clearly attached.<br>
|
|
||||||
<ion-img src="assets/images/ispex-mount4.png"></ion-img>
|
|
||||||
6. Gently move the smartphone back
|
|
||||||
and forth to ensure the add-on is
|
|
||||||
firmly attached.
|
|
||||||
If the add-on appears to rotate or slide off, start again. </ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<canvas id="canvasOutput"></canvas>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -1,75 +1,100 @@
|
|||||||
import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
import * as Parse from 'parse';
|
||||||
|
import { ENV } from '../../app.constant';
|
||||||
|
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { ModalController } from '@ionic/angular';
|
import { ModalController } from '@ionic/angular';
|
||||||
import { PreviewPage } from './preview/preview.page';
|
import { PreviewPage } from './preview/preview.page';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
|
import { Storage } from '@ionic/storage-angular';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
declare var cv: any;
|
Parse.initialize(ENV.parseAppId, ENV.parseJSKey);
|
||||||
|
(Parse as any).serverURL = ENV.parseServerUrl; // use your server url
|
||||||
|
|
||||||
|
let uploadSuccess = false;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-ispex',
|
selector: 'app-ispex',
|
||||||
templateUrl: './ispex.page.html',
|
templateUrl: './ispex.page.html',
|
||||||
styleUrls: ['./ispex.page.scss'],
|
styleUrls: ['./ispex.page.scss'],
|
||||||
})
|
})
|
||||||
|
export class IspexPage implements OnInit {
|
||||||
|
uploadSuccess = false; // Add this line
|
||||||
|
image = '';
|
||||||
|
user = null;
|
||||||
|
language = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
export class IspexPage implements AfterViewInit {
|
private modal: ModalController,
|
||||||
|
private translateService: TranslateService,
|
||||||
@ViewChild('imageCanvas', { static: false }) canvasEl : ElementRef;
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth,
|
||||||
@ViewChild('image', { static: false }) imageEl : ElementRef;
|
private storage: Storage,
|
||||||
|
private router: Router
|
||||||
image = null;
|
) {
|
||||||
constructor(private modal: ModalController) {}
|
user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
ngAfterViewInit() {
|
this.user = response;
|
||||||
|
console.log(this.user.uid);
|
||||||
|
});
|
||||||
// this._CANVAS = this.canvasEl.nativeElement;
|
|
||||||
// this._IMAGE = this.imageEl.nativeElement;
|
|
||||||
|
|
||||||
console.log ("ngAfterViewInit");
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
async openCamera() {
|
async openCamera() {
|
||||||
const modal = await this.modal.create({
|
const modal = await this.modal.create({
|
||||||
component: PreviewPage,
|
component: PreviewPage,
|
||||||
cssClass: 'fullscreen',
|
cssClass: 'fullscreen',
|
||||||
animated: true
|
animated: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.onDidDismiss().then((data) => {
|
modal.onDidDismiss().then((data) => {
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
this.image = data.data;
|
this.image = data.data;
|
||||||
|
|
||||||
|
this.uploadSuccess = true;
|
||||||
|
// create parse class
|
||||||
|
|
||||||
let imgElement = document.getElementById('img');
|
const myspex_data_store = Parse.Object.extend('myspex_data');
|
||||||
let src = cv.imread(imgElement);
|
|
||||||
let dst = new cv.Mat();
|
|
||||||
let dsize = new cv.Size(src.rows, src.cols);
|
|
||||||
let center = new cv.Point(src.cols / 2, src.rows / 2);
|
|
||||||
let M = cv.getRotationMatrix2D(center, -90, 1);
|
|
||||||
cv.warpAffine(src, dst, M, dsize, cv.INTER_LINEAR, cv.BORDER_CONSTANT, new cv.Scalar());
|
|
||||||
cv.imshow('canvasOutput', dst);
|
|
||||||
src.delete(); dst.delete(); M.delete();
|
|
||||||
|
|
||||||
|
// create new instance of parse class
|
||||||
|
|
||||||
cv.imshow('canvasOutput', dst);
|
const myspex_data = new myspex_data_store();
|
||||||
src.delete(); dst.delete();
|
|
||||||
|
|
||||||
|
// set value for parse clas
|
||||||
|
|
||||||
|
const file = new Parse.File('image.jpg', { base64: this.image });
|
||||||
|
file.save().then(
|
||||||
|
(file) => {
|
||||||
|
console.log(file);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
myspex_data.set('user_uid', this.user.uid);
|
||||||
|
myspex_data.set('name', this.user.displayName);
|
||||||
|
myspex_data.set('email', this.user.email);
|
||||||
|
myspex_data.set('image', file);
|
||||||
|
|
||||||
}
|
myspex_data.save().then(
|
||||||
|
(result: any) => {
|
||||||
|
console.log(result);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('no data');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return await modal.present();
|
return await modal.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// navigate to the classify page
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-content id="content" [fullscreen]="true">
|
<ion-content id="content" [fullscreen]="true">
|
||||||
|
|
||||||
<div *ngIf="cameraActive">
|
<div *ngIf="cameraActive">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-button (click)="stopCamera()" expand="block" id="close">
|
<ion-button (click)="stopCamera()" expand="block" id="close">
|
||||||
X</ion-button>
|
<ion-icon slot="icon-only" name="close-circle"></ion-icon>
|
||||||
|
|
||||||
|
|
||||||
<ion-button (click)="takePicture(1)" expand="block" id="grey">
|
|
||||||
Grey
|
|
||||||
</ion-button>
|
|
||||||
<ion-button (click)="takePicture(2)" expand="block" id="sky">
|
|
||||||
Sky
|
|
||||||
</ion-button>
|
|
||||||
<ion-button (click)="takePicture(3)" expand="block" id="water">
|
|
||||||
Water
|
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ion-button (click)="takePicture(4)" expand="block" id="free">
|
<ion-button (click)="takePicture()" expand="block" id="capture">
|
||||||
Free
|
<ion-icon slot="icon-only" name="camera"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -1,65 +1,42 @@
|
|||||||
|
ion-content {
|
||||||
|
--background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#capture {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: calc(50% - 25px);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flip {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: calc(50% + 125px);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
#close {
|
#close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
left: calc(50% - 160px);
|
left: calc(50% - 175px);
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rgb {
|
#capture::part(native) {
|
||||||
position: absolute;
|
border-radius: 30px;
|
||||||
bottom: 30px;
|
|
||||||
left: calc(50% - 140px);
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
z-index: 99999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#close::part(native) {
|
||||||
|
border-radius: 30px;
|
||||||
|
|
||||||
#grey {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 30px;
|
|
||||||
left: calc(50% - 80px);
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
z-index: 99999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#flip::part(native) {
|
||||||
|
border-radius: 30px;
|
||||||
#sky {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 30px;
|
|
||||||
left: calc(50% - 20px);
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
z-index: 99999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#water{
|
|
||||||
position: absolute;
|
|
||||||
bottom: 30px;
|
|
||||||
left: calc(50% + 40px);
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
z-index: 99999;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#free {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 30px;
|
|
||||||
left: calc(50% + 100px);
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
z-index: 99999;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-content { --background: black;}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
import * as Parse from 'parse';
|
|
||||||
import { ENV } from '../../../app.constant';
|
|
||||||
import { Vibration } from '@ionic-native/vibration/ngx';
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Plugins } from "@capacitor/core"
|
import { Plugins } from "@capacitor/core"
|
||||||
const { CameraPreview } = Plugins;
|
const { CameraPreview } = Plugins;
|
||||||
import { CameraPreviewOptions, CameraPreviewPictureOptions, CameraSampleOptions } from '@capacitor-community/camera-preview';
|
import { CameraPreviewOptions, CameraPreviewPictureOptions } from '@capacitor-community/camera-preview';
|
||||||
|
import '@capacitor-community/camera-preview';
|
||||||
import { ModalController } from '@ionic/angular';
|
import { ModalController } from '@ionic/angular';
|
||||||
import { Geolocation} from '@capacitor/geolocation';
|
|
||||||
|
|
||||||
import { Device } from '@capacitor/device';
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-preview',
|
selector: 'app-preview',
|
||||||
@@ -17,243 +11,46 @@ import { Device } from '@capacitor/device';
|
|||||||
styleUrls: ['./preview.page.scss'],
|
styleUrls: ['./preview.page.scss'],
|
||||||
})
|
})
|
||||||
export class PreviewPage implements OnInit {
|
export class PreviewPage implements OnInit {
|
||||||
image = null;
|
image ="";
|
||||||
cameraActive = false;
|
cameraActive = false;
|
||||||
private parseAppId: string = ENV.parseAppId;
|
constructor(private modal: ModalController) { }
|
||||||
private parseServerUrl: string = ENV.parseServerUrl;
|
|
||||||
private parseJSKey: string=ENV.parseJSKey;
|
|
||||||
public datetime_ux: string;
|
|
||||||
public datetime: string;
|
|
||||||
|
|
||||||
public latitude: number;
|
|
||||||
public longitude: number;
|
|
||||||
public altitude: number;
|
|
||||||
public heading: number;
|
|
||||||
|
|
||||||
// public deviceAppVersion: string;
|
|
||||||
// public deviceAppBuild: string;
|
|
||||||
public deviceOsVersion: string;
|
|
||||||
public devicePlatform: string;
|
|
||||||
|
|
||||||
public deviceManufacturer: string;
|
|
||||||
public deviceModel: string;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(private modal: ModalController, private vibration: Vibration
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
|
|
||||||
this.getLocation();
|
|
||||||
this.parseInitialize();
|
|
||||||
this.launchCamera();
|
this.launchCamera();
|
||||||
|
}
|
||||||
this.getDeviceInfo();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
launchCamera() {
|
launchCamera() {
|
||||||
const cameraPreviewOptions: CameraPreviewOptions = {
|
const cameraPreviewOptions: CameraPreviewOptions = {
|
||||||
position: 'rear', // front or rear
|
position: 'front', // front or rear
|
||||||
parent: 'content', // the id on the ion-content
|
parent: 'content', // the id on the ion-content
|
||||||
className: '',
|
className: '',
|
||||||
width: window.screen.width, //width of the camera display
|
width: window.screen.width, //width of the camera display
|
||||||
height:window.screen.height-200, //height of the camera
|
height: window.screen.height - 200, //height of the camera
|
||||||
|
|
||||||
toBack: false,
|
toBack: false,
|
||||||
disableAudio: true,
|
disableAudio: true,
|
||||||
rotateWhenOrientationChanged: false,
|
enableHighResolution: true,
|
||||||
lockAndroidOrientation: true
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
CameraPreview.start(cameraPreviewOptions);
|
CameraPreview['start'](cameraPreviewOptions);
|
||||||
this.cameraActive = true;
|
this.cameraActive = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async takePicture() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async takePicture( dngType ) {
|
|
||||||
|
|
||||||
|
|
||||||
// buzz for user feedback
|
|
||||||
|
|
||||||
this.vibration.vibrate(1);
|
|
||||||
|
|
||||||
|
|
||||||
// this captures a raw image. replace quality with type
|
|
||||||
const cameraPreviewPictureOptions: CameraPreviewPictureOptions = {
|
const cameraPreviewPictureOptions: CameraPreviewPictureOptions = {
|
||||||
quality: dngType// misuse quality: 0 normal (rgb) image 1 gray card 2 sky image 3 water image 4 other spectro(pol)
|
quality: 90
|
||||||
};
|
};
|
||||||
|
const result = await CameraPreview['capture'](cameraPreviewPictureOptions);
|
||||||
|
|
||||||
const result = await CameraPreview.capture(cameraPreviewPictureOptions);
|
|
||||||
this.image = `data:image/jpeg;base64,${result.value}`;
|
this.image = `data:image/jpeg;base64,${result.value}`;
|
||||||
|
|
||||||
|
|
||||||
this.getLocation();
|
|
||||||
|
|
||||||
// save to parse
|
|
||||||
|
|
||||||
|
|
||||||
const base64PictureData = result.value;
|
|
||||||
|
|
||||||
|
|
||||||
let d = new Date();
|
|
||||||
this.datetime=d.toString();
|
|
||||||
var unixTimeStamp = Math.floor(d.getTime() / 1000);
|
|
||||||
this.datetime_ux=unixTimeStamp.toString();
|
|
||||||
|
|
||||||
// do something with base64PictureData
|
|
||||||
|
|
||||||
var Base64SnapImage:string;
|
|
||||||
|
|
||||||
Base64SnapImage = "data:image/jpeg;base64," + base64PictureData;
|
|
||||||
|
|
||||||
|
|
||||||
var Base64DNGImage:string;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var Save2Parse = Parse.Object.extend('ispex_data');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var iSPEX2_store = new Save2Parse();
|
|
||||||
// set initial data record
|
|
||||||
|
|
||||||
|
|
||||||
// upload thumb for user feedback. DNG's are uploaded within the native part
|
|
||||||
|
|
||||||
|
|
||||||
const thumbFiletoUpload = new Parse.File("thumb.jpg", { base64: Base64SnapImage},"image/jpeg");
|
|
||||||
|
|
||||||
thumbFiletoUpload.save().then(function() {
|
|
||||||
console.log ("saved thumb!")
|
|
||||||
}, function(error) {
|
|
||||||
// The file either could not be read, or could not be saved to Parse.
|
|
||||||
|
|
||||||
console.log ("error saving thumb")
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
iSPEX2_store.set('thumb', thumbFiletoUpload);
|
|
||||||
|
|
||||||
iSPEX2_store.set('latitude',this.latitude);
|
|
||||||
iSPEX2_store.set('longitude',this.longitude);
|
|
||||||
iSPEX2_store.set('altitude',this.altitude);
|
|
||||||
iSPEX2_store.set('heading',this.heading);
|
|
||||||
|
|
||||||
iSPEX2_store.set('dngFileURL',result.dngFileURL);
|
|
||||||
iSPEX2_store.set('heading',result.heading);
|
|
||||||
iSPEX2_store.set('device_angle',result.device_angle);
|
|
||||||
|
|
||||||
|
|
||||||
iSPEX2_store.set ('device_osversion', this.deviceOsVersion);
|
|
||||||
iSPEX2_store.set ('device_platform', this.devicePlatform);
|
|
||||||
iSPEX2_store.set ('device_manufacturer', this.deviceManufacturer);
|
|
||||||
iSPEX2_store.set ('device_model', this.deviceModel);
|
|
||||||
|
|
||||||
|
|
||||||
iSPEX2_store.set ('datetimerecorded', this.datetime);
|
|
||||||
iSPEX2_store.set ('datetime_ux', this.datetime_ux);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await iSPEX2_store.save();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.stopCamera();
|
this.stopCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async getLocation() {
|
|
||||||
const position = await Geolocation.getCurrentPosition({enableHighAccuracy: true});
|
|
||||||
this.latitude = position.coords.latitude;
|
|
||||||
this.heading= position.coords.heading;
|
|
||||||
// console.log (position.coords.latitude);
|
|
||||||
this.longitude = position.coords.longitude;
|
|
||||||
this.altitude = position.coords.altitude;
|
|
||||||
return position.coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async takeSample() {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async stopCamera() {
|
async stopCamera() {
|
||||||
await CameraPreview.stop();
|
await CameraPreview['stop']();
|
||||||
this.modal.dismiss(this.image);
|
this.modal.dismiss(this.image);
|
||||||
}
|
}
|
||||||
|
|
||||||
async flipCamera() {
|
async flipCamera() {
|
||||||
await CameraPreview.flip();
|
await CameraPreview['flip']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getDeviceInfo() {
|
|
||||||
const info = await Device.getInfo();
|
|
||||||
// this.deviceAppVersion = info.appVersion;
|
|
||||||
// this.deviceAppBuild = info.appBuild;
|
|
||||||
this.deviceOsVersion = info.osVersion;
|
|
||||||
this.devicePlatform = info.platform;
|
|
||||||
this.deviceManufacturer = info.manufacturer;
|
|
||||||
this.deviceModel = info.model;
|
|
||||||
console.log(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private parseInitialize() {
|
|
||||||
|
|
||||||
Parse.initialize(this.parseAppId, this.parseJSKey);
|
|
||||||
|
|
||||||
(Parse as any).serverURL = this.parseServerUrl; // use your server url
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Prepare</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">PREPARE</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-title size="large">Prepare the device</ion-title>
|
<ion-title size="large">Prepare the device</ion-title>
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ Attach (optional) pH Strip</ion-item>
|
|||||||
4. Unlock spindle</ion-item>
|
4. Unlock spindle</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
|
||||||
<img src="assets/img/unlockspindle.png"></ion-item>
|
<img src="assets/images/unlockspindle.png"></ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ Attach (optional) pH Strip</ion-item>
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-button expand="block" routerLink="/instructions/step2" routerDirection="forward" >
|
<ion-button expand="block" routerLink="step2" routerDirection="forward" >
|
||||||
Step 2
|
Step 2
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>2. Transperancy</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">2. Transparancy</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ The app will now calculate the Secchi disk depth (re-appareance distance minus d
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-button expand="block" routerLink="/instructions/step3" routerDirection="forward" >
|
<ion-button expand="block" routerLink="../step3" routerDirection="forward" >
|
||||||
Step 3
|
Step 3
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>3. Disk color</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">3. Disk color</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ The app will now calculate the Secchi disk depth (re-appareance distance minus d
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
|
||||||
<ion-button expand="block" routerLink="/instructions/step4" routerDirection="forward" >
|
<ion-button expand="block" routerLink="../step4" routerDirection="forward" >
|
||||||
Step 4
|
Step 4
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>4. Color of the water</ion-title>
|
<ion-buttons slot="start">
|
||||||
<ion-buttons slot="start">
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-back-button></ion-back-button>
|
</ion-buttons>
|
||||||
</ion-buttons>
|
<ion-title class="ion-text-center">4. Color of the water</ion-title>
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
|
||||||
|
|
||||||
Colour of the water surface
|
Colour of the water surface
|
||||||
Estimate the Forel-Ule colour scale by looking at the water surface, away from sun glitter.
|
Estimate the Forel-Ule colour scale by looking at the water surface, away from sun glitter.
|
||||||
</ion-item>
|
</ion-item>
|
||||||
@@ -26,7 +22,7 @@ Estimate the Forel-Ule colour scale by looking at the water surface, away from s
|
|||||||
|
|
||||||
Keeping the sun diagonally behind you over your left or right shoulder, look down and forward to the water surface (40-45 degrees) and compare the colour of the water to the Forel-Ule index. Optionally upload another photo using the same viewing angles.</ion-item>
|
Keeping the sun diagonally behind you over your left or right shoulder, look down and forward to the water surface (40-45 degrees) and compare the colour of the water to the Forel-Ule index. Optionally upload another photo using the same viewing angles.</ion-item>
|
||||||
<img src="assets/images/forelule.png"><br>
|
<img src="assets/images/forelule.png"><br>
|
||||||
<ion-button expand="block" routerLink="/instructions/step5" routerDirection="forward" >
|
<ion-button expand="block" routerLink="../step5" routerDirection="forward" >
|
||||||
|
|
||||||
|
|
||||||
Step 5
|
Step 5
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>5. Additional</ion-title>
|
<ion-buttons slot="start">
|
||||||
<ion-buttons slot="start">
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-back-button></ion-back-button>
|
</ion-buttons>
|
||||||
</ion-buttons>
|
<ion-title class="ion-text-center">5. Additional</ion-title>
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
@@ -27,6 +25,6 @@ Natural waters are usually only slightly acidic (6) or alkalic (8). The acidity
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-button expand="block" routerLink="/home" routerDirection="root">Finish
|
<ion-button expand="block" routerLink="/" routerDirection="root">Finish
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Colour at half depth</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">Colour at half depth</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|||||||
@@ -1,120 +1,127 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Additional</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button
|
||||||
<ion-buttons slot="start">
|
defaultHref="/"
|
||||||
<ion-back-button></ion-back-button>
|
icon="chevron-back-outline"
|
||||||
</ion-buttons>
|
text=""
|
||||||
|
></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title class="ion-text-center">Additional</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon name="warning-outline" color="danger"></ion-icon>You can now wind up the
|
||||||
|
Secchi disk mechanism.
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-card><ion-icon name="warning-outline"></ion-icon>You can now wind up the Secchi disk mechanism.</ion-card>
|
<ion-item>
|
||||||
|
<ion-label> <b>Acidity</b></ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
If you used pH paper, compare the colour of the pH strip to the pH colour
|
||||||
<b>Acidity</b></ion-label>
|
index provided.
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item> <img src="assets/images/ph.png" /><br /> </ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label> The pH value is (1-14):</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
[(ngModel)]="phvalue"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
value="1"
|
||||||
|
placeholder="Enter pH value here"
|
||||||
|
></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label><b> Surface water colour</b> </ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
You can upload a photo of the colour of the water, avoiding reflections from
|
||||||
|
the sun: Hold the camera 40-45 degrees from horizontal towards the water
|
||||||
|
surface. With the sun straight at your back rotate 45 degrees to your left
|
||||||
|
or your right to have the sun diagonally over your left or right shoulder.
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item> <img src="assets/images/bodypos.png" /><br /> </ion-item>
|
||||||
If you used pH paper, compare the colour of the pH strip to the pH colour index provided.
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
<img src="assets/images/ph.png"><br>
|
<ion-item>
|
||||||
</ion-item>
|
To add a photo click here:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
<ion-label> The pH value is (1-14):</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
<ion-input [(ngModel)]="phvalue" type="number" inputmode="numeric" value="1" placeholder="Enter pH value here"></ion-input>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-label><b>
|
|
||||||
Surface water colour</b>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
You can upload a photo of the colour of the water, avoiding reflections from the sun:
|
|
||||||
Hold the camera 40-45 degrees from horizontal towards the water surface.
|
|
||||||
With the sun straight at your back rotate 45 degrees to your left or your right to have the sun diagonally over your left or right shoulder.
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<img src="assets/images/bodypos.png"><br>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
To add a photo click here:
|
|
||||||
|
|
||||||
<ion-button (click)="takePicture()">
|
<ion-button (click)="takePicture()">
|
||||||
<ion-icon name="camera"></ion-icon>
|
<ion-icon name="camera"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<img [src]="this.ColouratSurfacePictureTaken"/>
|
<img [src]="this.ColouratSurfacePictureTaken" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label
|
||||||
|
>Looking at the water surface in this direction, the colour of the water
|
||||||
|
is <br />
|
||||||
|
most similar to Forel-Ule colour number (1-21):
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
[(ngModel)]="colouratsurface"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
placeholder="Enter colour value here"
|
||||||
|
></ion-input
|
||||||
|
></ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label><b>Quality control check-list</b></ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-checkbox slot="start" [(ngModel)]="bottom_visible"></ion-checkbox>
|
||||||
|
<ion-label class="ion-text-wrap"
|
||||||
|
>The bottom was visible / the disk reached the bottom while I could still
|
||||||
|
see it.</ion-label
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-checkbox slot="start" [(ngModel)]="end_of_tape"></ion-checkbox>
|
||||||
|
<ion-label class="ion-text-wrap"
|
||||||
|
>I reached the end of the tape while I could still see the disk</ion-label
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-checkbox slot="start"></ion-checkbox
|
||||||
|
><ion-label class="ion-text-wrap">
|
||||||
|
I was not able to keep the disk going down at a straight angle. The
|
||||||
|
estimated distance angle is:</ion-label
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-input
|
||||||
|
[(ngModel)]="angle_estimated"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
value="01"
|
||||||
|
placeholder="Enter"
|
||||||
|
></ion-input
|
||||||
|
><ion-label class="ion-text-wrap"> degrees from vertical.</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-button
|
||||||
<ion-item>
|
expand="block"
|
||||||
<ion-label>Looking at the water surface in this direction, the colour of the water is <br> most similar to Forel-Ule colour number (1-21):
|
(click)="validate()"
|
||||||
</ion-label>
|
routerLink="/ms_measure/qccheck"
|
||||||
</ion-item>
|
routerDirection="forward"
|
||||||
|
>
|
||||||
<ion-item>
|
Go to step 5
|
||||||
|
</ion-button>
|
||||||
<ion-input [(ngModel)]="colouratsurface" type="number" inputmode="numeric" placeholder="Enter colour value here"></ion-input></ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-label><b>Quality control check-list</b></ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item >
|
|
||||||
<ion-checkbox slot="start" [(ngModel)]="bottom_visible"></ion-checkbox>
|
|
||||||
<ion-label class="ion-text-wrap">The bottom was visible / the disk reached the bottom while I could still see it.</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-checkbox slot="start" [(ngModel)]="end_of_tape"></ion-checkbox> <ion-label class="ion-text-wrap">I reached the end of the tape while I could still see the disk</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item>
|
|
||||||
<ion-checkbox slot="start"></ion-checkbox><ion-label class="ion-text-wrap">
|
|
||||||
I was not able to keep the disk going down at a straight angle. The estimated distance angle is:</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item>
|
|
||||||
<ion-input [(ngModel)]="angle_estimated" type="number" inputmode="numeric" value="01" placeholder="Enter" ></ion-input><ion-label class="ion-text-wrap">
|
|
||||||
degrees from vertical.</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-button expand="block" (click)="validate()" routerLink="/ms_measure/qccheck" routerDirection="forward">
|
|
||||||
Go to step 5
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Distance to water</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">Distance to water</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
Wind the disk down to the water surface and record the distance from the handheld device to the water surface. Keep your arm at the same distance from the water for the remaining depth measurements.
|
Wind the disk down to the water surface and record the distance from the handheld device to the water surface. Keep your arm at the same distance from the water for the remaining depth measurements.
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Safety first</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">Safety first</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label> <ion-icon name="warning-outline"></ion-icon>
|
<ion-label>
|
||||||
Safety first</ion-label>
|
<ion-icon name="warning-outline" color="danger"></ion-icon> Safety first</ion-label
|
||||||
|
>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
Work from a stable platform, wear a life vest and make sure someone knows
|
||||||
|
where you are. Only upload photos if you can do so without risking your
|
||||||
|
safety and your phone. Find a spot where you can look into the water, away
|
||||||
|
from sun reflections. Record Secchi depth, water colour and pH without
|
||||||
|
sunglasses.
|
||||||
|
<br />
|
||||||
|
If you have pH paper, clip a new section into the provided clip and attach
|
||||||
|
it to the disk.
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<ion-item>
|
||||||
|
<img src="assets/images/attachph.png" />
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
Work from a stable platform, wear a life vest and make sure someone knows where you are. Only upload photos if you can do so without risking your safety and your phone.
|
<ion-button
|
||||||
Find a spot where you can look into the water, away from sun reflections. Record Secchi depth, water colour and pH without sunglasses.
|
expand="block"
|
||||||
<br>
|
routerLink="distancetowater"
|
||||||
If you have pH paper, clip a new section into the provided clip and attach it to the disk.
|
routerDirection="forward"
|
||||||
|
>
|
||||||
</ion-item>
|
Go to step 1
|
||||||
|
</ion-button>
|
||||||
<br>
|
|
||||||
<ion-item>
|
|
||||||
<img src="assets/images/attachph.png">
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<ion-button expand="block" routerLink="distancetowater" routerDirection="forward">
|
|
||||||
Go to step 1
|
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Review</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">Review</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { Storage } from '@ionic/storage-angular';
|
|||||||
import { Guid } from "guid-typescript";
|
import { Guid } from "guid-typescript";
|
||||||
import * as Parse from 'parse';
|
import * as Parse from 'parse';
|
||||||
import { ENV } from '../../../../app.constant';
|
import { ENV } from '../../../../app.constant';
|
||||||
import { Geolocation} from '@capacitor/geolocation';
|
import { Geolocation } from '@capacitor/geolocation';
|
||||||
|
import { AuthService } from '../../../../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-qccheck',
|
selector: 'app-qccheck',
|
||||||
@@ -11,143 +14,150 @@ import { Geolocation} from '@capacitor/geolocation';
|
|||||||
styleUrls: ['./qccheck.page.scss'],
|
styleUrls: ['./qccheck.page.scss'],
|
||||||
})
|
})
|
||||||
export class QccheckPage implements OnInit {
|
export class QccheckPage implements OnInit {
|
||||||
// now get all the values
|
// now get all the values
|
||||||
private parseAppId: string = ENV.parseAppId;
|
private parseAppId: string = ENV.parseAppId;
|
||||||
private parseServerUrl: string = ENV.parseServerUrl;
|
private parseServerUrl: string = ENV.parseServerUrl;
|
||||||
private parseJSKey: string=ENV.parseJSKey;
|
private parseJSKey: string = ENV.parseJSKey;
|
||||||
|
user = null;
|
||||||
public swversion_number:string;
|
language= '';
|
||||||
public swversion_code:number;
|
public swversion_number: string;
|
||||||
public latitude: number;
|
public swversion_code: number;
|
||||||
public longitude: number;
|
public latitude: number;
|
||||||
public distancetowater: number;
|
public longitude: number;
|
||||||
public reappear: number;
|
public distancetowater: number;
|
||||||
public colourathalfdepth: number;
|
public reappear: number;
|
||||||
public colourathalfdepthimage: string;
|
public colourathalfdepth: number;
|
||||||
public colouratsurface: number;
|
public colourathalfdepthimage: string;
|
||||||
public colouratsurfaceimage: string;
|
public colouratsurface: number;
|
||||||
public phvalue: number;
|
public colouratsurfaceimage: string;
|
||||||
public bottom_visible: string;
|
public phvalue: number;
|
||||||
public end_of_tape: string;
|
public bottom_visible: string;
|
||||||
public angle_estimated: number;
|
public end_of_tape: string;
|
||||||
public datetime: Date;
|
public angle_estimated: number;
|
||||||
public datetime_ux: string;
|
public datetime: Date;
|
||||||
public secchi_depth: string;
|
public datetime_ux: string;
|
||||||
|
public secchi_depth: string;
|
||||||
|
|
||||||
|
|
||||||
public rec_uid: string;
|
public rec_uid: string;
|
||||||
|
|
||||||
newSecchi = { uid: null, swversion_number:null, swversion_code:null, latitude: null, longitude: null, distancetowater: null, reappear: null, colourathalfdepth: null, colourathalfdepthimage: null, colouratsurface: null, colouratsurfaceimage: null, datetimerecorded: null, datetime_ux:null, bottom_visible:null, end_of_tape:null, phvalue:null, angle_estimated:null, secchi_depth:null };
|
newSecchi = { uid: null, swversion_number: null, swversion_code: null, latitude: null, longitude: null, distancetowater: null, reappear: null, colourathalfdepth: null, colourathalfdepthimage: null, colouratsurface: null, colouratsurfaceimage: null, datetimerecorded: null, datetime_ux: null, bottom_visible: null, end_of_tape: null, phvalue: null, angle_estimated: null, secchi_depth: null };
|
||||||
|
|
||||||
constructor(private storage: Storage) {
|
|
||||||
|
|
||||||
|
constructor(private storage: Storage, private translateService: TranslateService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth) {
|
||||||
|
user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
|
this.user = response;
|
||||||
|
console.log (this.user.uid)
|
||||||
|
});
|
||||||
this.rec_uid = Guid.raw(); // make it a string
|
this.rec_uid = Guid.raw(); // make it a string
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.storage.create();
|
this.storage.create();
|
||||||
this.parseInitialize();
|
this.parseInitialize();
|
||||||
this.getLocation();
|
this.getLocation();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('swversion_number').then((val) => {
|
this.storage.get('swversion_number').then((val) => {
|
||||||
|
|
||||||
this.swversion_number= val;
|
this.swversion_number = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.storage.get('swversion_code').then((val) => {
|
this.storage.get('swversion_code').then((val) => {
|
||||||
|
|
||||||
this.swversion_code= val;
|
this.swversion_code = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('latitude').then((val) => {
|
this.storage.get('latitude').then((val) => {
|
||||||
|
|
||||||
this.latitude= val;
|
this.latitude = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.storage.get('longitude').then((val) => {
|
this.storage.get('longitude').then((val) => {
|
||||||
|
|
||||||
this.longitude= val;
|
this.longitude = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('distancetowater').then((val) => {
|
this.storage.get('distancetowater').then((val) => {
|
||||||
|
|
||||||
this.distancetowater= val;
|
this.distancetowater = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('reappear').then((val) => {
|
this.storage.get('reappear').then((val) => {
|
||||||
|
|
||||||
this.reappear= val;
|
this.reappear = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('colourathalfdepth').then((val) => {
|
this.storage.get('colourathalfdepth').then((val) => {
|
||||||
|
|
||||||
this.colourathalfdepth= val;
|
this.colourathalfdepth = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('colouratsurface').then((val) => {
|
this.storage.get('colouratsurface').then((val) => {
|
||||||
|
|
||||||
this.colouratsurface= val;
|
this.colouratsurface = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('colourathalfdepthimage').then((val) => {
|
this.storage.get('colourathalfdepthimage').then((val) => {
|
||||||
// check if it is null
|
// check if it is null
|
||||||
if (val==null) {
|
if (val == null) {
|
||||||
console.log("colourathalfdepthimage is null");
|
console.log("colourathalfdepthimage is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// convert to Parse file
|
// convert to Parse file
|
||||||
val = new Parse.File("colourathalfdepthimage.png", { base64: val},"image/png");
|
val = new Parse.File("colourathalfdepthimage.png", { base64: val }, "image/png");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.colourathalfdepthimage= val;
|
this.colourathalfdepthimage = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('colouratsurfaceimage').then((val) => {
|
this.storage.get('colouratsurfaceimage').then((val) => {
|
||||||
// check if it is null
|
// check if it is null
|
||||||
if (val==null) {
|
if (val == null) {
|
||||||
console.log("colouratsurfaceimage is null");
|
console.log("colouratsurfaceimage is null");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// convert to Parse file
|
// convert to Parse file
|
||||||
val = new Parse.File("colouratsurfaceimage.png", { base64: val},"image/png");
|
val = new Parse.File("colouratsurfaceimage.png", { base64: val }, "image/png");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.colouratsurfaceimage= val;
|
this.colouratsurfaceimage = val;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -156,7 +166,7 @@ this.colouratsurfaceimage= val;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -165,107 +175,111 @@ this.colouratsurfaceimage= val;
|
|||||||
|
|
||||||
this.storage.get('phvalue').then((val) => {
|
this.storage.get('phvalue').then((val) => {
|
||||||
|
|
||||||
this.phvalue= val;
|
this.phvalue = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('bottom_visible').then((val) => {
|
this.storage.get('bottom_visible').then((val) => {
|
||||||
|
|
||||||
this.bottom_visible= val;
|
this.bottom_visible = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.storage.get('end_of_tape').then((val) => {
|
this.storage.get('end_of_tape').then((val) => {
|
||||||
|
|
||||||
this.end_of_tape= val;
|
this.end_of_tape = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('angle_estimated').then((val) => {
|
this.storage.get('angle_estimated').then((val) => {
|
||||||
|
|
||||||
this.angle_estimated= val;
|
this.angle_estimated = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.storage.get('secchi_depth').then((val) => {
|
this.storage.get('secchi_depth').then((val) => {
|
||||||
|
|
||||||
this.secchi_depth= val;
|
this.secchi_depth = val;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let d = new Date();
|
let d = new Date();
|
||||||
|
|
||||||
this.datetime=d;
|
this.datetime = d;
|
||||||
|
|
||||||
var unixTimeStamp = Math.floor(d.getTime() / 1000);
|
var unixTimeStamp = Math.floor(d.getTime() / 1000);
|
||||||
this.datetime_ux=unixTimeStamp.toString();
|
this.datetime_ux = unixTimeStamp.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate() {
|
async validate() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// now save to Parse
|
// now save to Parse
|
||||||
|
|
||||||
var secchi_data = Parse.Object.extend('mini_secchi_data');
|
var secchi_data = Parse.Object.extend('mini_secchi_data');
|
||||||
var secchi_store = new secchi_data();
|
var secchi_store = new secchi_data();
|
||||||
// set initial data record
|
// set initial data record
|
||||||
console.log("saving to Parse");
|
console.log("saving to Parse");
|
||||||
|
|
||||||
secchi_store.uid= this.rec_uid;
|
secchi_store.set('uid', this.rec_uid);
|
||||||
secchi_store.latitude=this.latitude;
|
secchi_store.set('latitude', this.latitude);
|
||||||
secchi_store.longitude=this.longitude;
|
secchi_store.set('longitude', this.longitude);
|
||||||
secchi_store.distancetowater=this.distancetowater;
|
secchi_store.set('distancetowater', this.distancetowater);
|
||||||
secchi_store.reappear=this.reappear;
|
secchi_store.set('reappear', this.reappear);
|
||||||
secchi_store.colourathalfdepth=this.colourathalfdepth;
|
secchi_store.set('colourathalfdepth', this.colourathalfdepth);
|
||||||
secchi_store.colourathalfdepthimage=this.colourathalfdepthimage;
|
secchi_store.set('colourathalfdepthimage', this.colourathalfdepthimage);
|
||||||
secchi_store.colouratsurface=this.colouratsurface;
|
secchi_store.set('colouratsurface', this.colouratsurface);
|
||||||
secchi_store.colouratsurfaceimage=this.colouratsurfaceimage;
|
secchi_store.set('colouratsurfaceimage', this.colouratsurfaceimage);
|
||||||
secchi_store.phvalue=this.phvalue;
|
secchi_store.set('phvalue', this.phvalue);
|
||||||
secchi_store.bottom_visible=this.bottom_visible;
|
secchi_store.set('bottom_visible', this.bottom_visible);
|
||||||
secchi_store.end_of_tape=this.end_of_tape;
|
secchi_store.set('end_of_tape', this.end_of_tape);
|
||||||
secchi_store.angle_estimated=this.angle_estimated;
|
secchi_store.set('angle_estimated', this.angle_estimated);
|
||||||
secchi_store.secchi_depth=this.secchi_depth;
|
secchi_store.set('secchi_depth', this.secchi_depth);
|
||||||
secchi_store.datetime_ux=this.datetime_ux.toString();
|
secchi_store.set('datetime_ux', this.datetime_ux.toString());
|
||||||
secchi_store.datetimerecorded=this.datetime.toISOString();
|
secchi_store.set('datetimerecorded', this.datetime.toISOString());
|
||||||
console.log ('saving to Parse', secchi_store);
|
secchi_store.set('user_uid', this.user.uid);
|
||||||
|
secchi_store.set('name', this.user.displayName);
|
||||||
|
secchi_store.set('email', this.user.email);
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await secchi_store.save();
|
|
||||||
console.log('Data saved successfully:', result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error saving data:', error);
|
|
||||||
// You could add additional handling for the error here, such as displaying a message to the user or retrying the save operation.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
try {
|
||||||
|
const result = await secchi_store.save();
|
||||||
|
console.log('Data saved successfully:', result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving data:', error);
|
||||||
|
// You could add additional handling for the error here, such as displaying a message to the user or retrying the save operation.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async getLocation() {
|
|
||||||
const position = await Geolocation.getCurrentPosition({enableHighAccuracy: true});
|
|
||||||
this.latitude = position.coords.latitude;
|
|
||||||
// console.log (position.coords.latitude);
|
|
||||||
this.longitude = position.coords.longitude;
|
|
||||||
return position.coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseInitialize() {
|
|
||||||
|
|
||||||
Parse.initialize(this.parseAppId, this.parseJSKey);
|
}
|
||||||
|
|
||||||
(Parse as any).serverURL = this.parseServerUrl; // use your server url
|
|
||||||
|
async getLocation() {
|
||||||
|
const position = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });
|
||||||
|
this.latitude = position.coords.latitude;
|
||||||
|
// console.log (position.coords.latitude);
|
||||||
|
this.longitude = position.coords.longitude;
|
||||||
|
return position.coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseInitialize() {
|
||||||
|
|
||||||
|
Parse.initialize(this.parseAppId, this.parseJSKey);
|
||||||
|
|
||||||
|
(Parse as any).serverURL = this.parseServerUrl; // use your server url
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<ion-header>
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar>
|
<ion-toolbar color="black">
|
||||||
<ion-title>Disk reappearance</ion-title>
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-buttons slot="start">
|
</ion-buttons>
|
||||||
<ion-back-button></ion-back-button>
|
<ion-title class="ion-text-center">Disk reappearance</ion-title>
|
||||||
</ion-buttons>
|
|
||||||
|
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,61 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-title>New Mini Secchi observation</ion-title>
|
<ion-title>Mini Secchi</ion-title>
|
||||||
|
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-menu-button slot="start"></ion-menu-button>
|
<ion-back-button defaultHref="/" icon="chevron-back-outline" text=""></ion-back-button>
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
|
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
<ion-title>Mini Secchi</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
<div class="ion-text-center">
|
||||||
|
<img src="assets/images/secchidisk.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
<ion-item>
|
<ion-button
|
||||||
<img src="assets/images/secchidisk.png"><br>
|
shape="round"
|
||||||
</ion-item>
|
expand="block"
|
||||||
|
routerLink="/ms_measure"
|
||||||
<ion-button shape="round" expand="block" routerLink="/ms_measure" strong="true" routerDirection="forward" color="white" style="background-image: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(86,143,150) 25%, rgba(123,166,84) 35%, rgba(149,182,69) 50%, rgba(173,181,95) 70%, rgba(161,77,4) 100%)" >
|
strong="true"
|
||||||
New observation
|
routerDirection="forward"
|
||||||
</ion-button>
|
color="white"
|
||||||
|
style="
|
||||||
<ion-button shape="round" color="white" expand="block" routerLink="/ms_instructions" strong="true" routerDirection="forward" color="white" style="background-image: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(86,143,150) 25%, rgba(123,166,84) 35%, rgba(149,182,69) 50%, rgba(173,181,95) 70%, rgba(161,77,4) 100%)" >
|
background-image: linear-gradient(
|
||||||
Instructions
|
90deg,
|
||||||
</ion-button>
|
rgba(2, 0, 36, 1) 0%,
|
||||||
|
rgba(86, 143, 150) 25%,
|
||||||
|
rgba(123, 166, 84) 35%,
|
||||||
|
rgba(149, 182, 69) 50%,
|
||||||
|
rgba(173, 181, 95) 70%,
|
||||||
|
rgba(161, 77, 4) 100%
|
||||||
|
);
|
||||||
|
"
|
||||||
</ion-content>
|
>
|
||||||
|
New observation
|
||||||
|
</ion-button>
|
||||||
|
|
||||||
|
<ion-button
|
||||||
|
shape="round"
|
||||||
|
color="white"
|
||||||
|
expand="block"
|
||||||
|
routerLink="/ms_instructions"
|
||||||
|
strong="true"
|
||||||
|
routerDirection="forward"
|
||||||
|
color="white"
|
||||||
|
style="
|
||||||
|
background-image: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(2, 0, 36, 1) 0%,
|
||||||
|
rgba(86, 143, 150) 25%,
|
||||||
|
rgba(123, 166, 84) 35%,
|
||||||
|
rgba(149, 182, 69) 50%,
|
||||||
|
rgba(173, 181, 95) 70%,
|
||||||
|
rgba(161, 77, 4) 100%
|
||||||
|
);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Instructions
|
||||||
|
</ion-button>
|
||||||
|
</ion-content>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { ClassifyPage } from './classify.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ClassifyPage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class ClassifyPageRoutingModule {}
|
||||||
20
src/app/mobisplugins/plantnet/classify/classify.module.ts
Normal file
20
src/app/mobisplugins/plantnet/classify/classify.module.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { ClassifyPageRoutingModule } from './classify-routing.module';
|
||||||
|
|
||||||
|
import { ClassifyPage } from './classify.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
IonicModule,
|
||||||
|
ClassifyPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [ClassifyPage]
|
||||||
|
})
|
||||||
|
export class ClassifyPageModule {}
|
||||||
33
src/app/mobisplugins/plantnet/classify/classify.page.html
Normal file
33
src/app/mobisplugins/plantnet/classify/classify.page.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Image classification</ion-title>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-item>
|
||||||
|
Best match:
|
||||||
|
|
||||||
|
<ion-label>
|
||||||
|
{{result}}
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-button expand="block" (click)="submit_image_with_classification()">Submit</ion-button>
|
||||||
|
|
||||||
|
|
||||||
|
</ion-content>
|
||||||
24
src/app/mobisplugins/plantnet/classify/classify.page.spec.ts
Normal file
24
src/app/mobisplugins/plantnet/classify/classify.page.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { ClassifyPage } from './classify.page';
|
||||||
|
|
||||||
|
describe('ClassifyPage', () => {
|
||||||
|
let component: ClassifyPage;
|
||||||
|
let fixture: ComponentFixture<ClassifyPage>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ClassifyPage ],
|
||||||
|
imports: [IonicModule.forRoot()]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ClassifyPage);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
225
src/app/mobisplugins/plantnet/classify/classify.page.ts
Normal file
225
src/app/mobisplugins/plantnet/classify/classify.page.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ENV } from '../../../app.constant';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from '../../../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
|
import { Storage } from '@ionic/storage-angular';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Geolocation } from '@capacitor/geolocation';
|
||||||
|
import { Http } from '@capacitor-community/http';
|
||||||
|
import * as Parse from 'parse';
|
||||||
|
|
||||||
|
// debug purpose: Chrome without CORS on mac
|
||||||
|
// open /Applications/Google\ Chrome.app --args --user-data-dir="/var/tmp/Chrome dev session" --disable-web-security
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-classify',
|
||||||
|
templateUrl: './classify.page.html',
|
||||||
|
styleUrls: ['./classify.page.scss'],
|
||||||
|
})
|
||||||
|
export class ClassifyPage implements OnInit {
|
||||||
|
PROJECT = 'all'; // try 'weurope' or 'canada'
|
||||||
|
API_URL = 'https://my-api.plantnet.org/v2/identify/' + this.PROJECT + '?api-key=';
|
||||||
|
API_PRIVATE_KEY = ENV.plantnetKey; // secret
|
||||||
|
API_SIMSEARCH_OPTION = '&include-related-images=true'; // optional: get most similar images
|
||||||
|
API_LANG = '&lang=en'; // default: en
|
||||||
|
// add result variabel for showing result to user
|
||||||
|
result = '';
|
||||||
|
|
||||||
|
parsefile: any;
|
||||||
|
parsefileName: any;
|
||||||
|
parsefileUrl: any;
|
||||||
|
|
||||||
|
uploadSuccess = false; // Add this line
|
||||||
|
image = '';
|
||||||
|
|
||||||
|
user = null;
|
||||||
|
language = '';
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
altitude: number;
|
||||||
|
constructor(
|
||||||
|
private storage: Storage,
|
||||||
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private router: Router
|
||||||
|
|
||||||
|
) {
|
||||||
|
user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
|
this.user = response;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
// get location
|
||||||
|
this.getLocation();
|
||||||
|
Parse.initialize(ENV.parseAppId, ENV.parseJSKey);
|
||||||
|
(Parse as any).serverURL = ENV.parseServerUrl; // use your server url
|
||||||
|
|
||||||
|
this.storage.create();
|
||||||
|
|
||||||
|
// get file out of storage and submit to parse
|
||||||
|
this.storage.get('plantnet_image').then((val) => { // get image from storage
|
||||||
|
this.image = val;
|
||||||
|
this.parsefile = new Parse.File('plantnet_image.jpg', { base64: this.image });
|
||||||
|
this.parsefile.save().then(
|
||||||
|
(response: any) => {
|
||||||
|
this.parsefileUrl = this.parsefile.toJSON().url;
|
||||||
|
this.parsefileName = this.parsefile.toJSON().name;
|
||||||
|
console.log('parsefileUrl', this.parsefileUrl);
|
||||||
|
console.log('parsefileName', this.parsefileName);
|
||||||
|
|
||||||
|
// call function to submit to plantnet
|
||||||
|
|
||||||
|
this.submit_to_plantnet();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
submit_image_with_classification() {
|
||||||
|
|
||||||
|
const plantnet_data_store = Parse.Object.extend('plantnet_data');
|
||||||
|
|
||||||
|
// create new instance of parse class
|
||||||
|
|
||||||
|
const plantnet_data = new plantnet_data_store();
|
||||||
|
|
||||||
|
// set value for parse clas
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
plantnet_data.set('user_uid', this.user.uid);
|
||||||
|
plantnet_data.set('name', this.user.displayName);
|
||||||
|
plantnet_data.set('email', this.user.email);
|
||||||
|
plantnet_data.set('image', this.parsefile);
|
||||||
|
plantnet_data.set('classification', this.result);
|
||||||
|
plantnet_data.set('latitude', this.latitude);
|
||||||
|
plantnet_data.set('longitude', this.longitude);
|
||||||
|
plantnet_data.set('altitude', this.altitude);
|
||||||
|
|
||||||
|
|
||||||
|
plantnet_data.save().then(
|
||||||
|
(result: any) => {
|
||||||
|
console.log(result);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// go to plantnet page
|
||||||
|
this.router.navigate(['/plantnet']);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLocation() {
|
||||||
|
const position = await Geolocation.getCurrentPosition({
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
});
|
||||||
|
this.latitude = position.coords.latitude;
|
||||||
|
console.log(position.coords.latitude);
|
||||||
|
this.longitude = position.coords.longitude;
|
||||||
|
this.altitude = position.coords.altitude;
|
||||||
|
return position.coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
submit_to_plantnet() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const imageUri = this.parsefileUrl;
|
||||||
|
const imageType = 'image/jpeg';
|
||||||
|
const imageName = this.parsefileName;
|
||||||
|
|
||||||
|
fetch(imageUri)
|
||||||
|
.then(response => response.blob())
|
||||||
|
.then(blob => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('images', new File([blob], imageName, { type: imageType }));
|
||||||
|
formData.append('organs', 'auto');
|
||||||
|
|
||||||
|
const url = 'https://my-api.plantnet.org/v2/identify/all';
|
||||||
|
const headers = {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
};
|
||||||
|
const params = {
|
||||||
|
'include-related-images': 'false',
|
||||||
|
'no-reject': 'false',
|
||||||
|
'lang': 'en',
|
||||||
|
'api-key': '2b10bmIKkNNcBL6D4jwq3il4rO'
|
||||||
|
};
|
||||||
|
|
||||||
|
Http.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
params: params,
|
||||||
|
data: formData
|
||||||
|
}).then(response => {
|
||||||
|
console.log(response.data);
|
||||||
|
|
||||||
|
// show result on to user
|
||||||
|
|
||||||
|
this.result = response.data.results[0].species.scientificName;
|
||||||
|
|
||||||
|
console.log(this.result);
|
||||||
|
|
||||||
|
|
||||||
|
}).catch(error => {
|
||||||
|
console.log ('error in response', error);
|
||||||
|
this.result = 'Species not found';
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// route to plantnet page
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,7 +7,16 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: PlantnetPage
|
component: PlantnetPage
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: 'classify',
|
||||||
|
loadChildren: () => import('./classify/classify.module').then( m => m.ClassifyPageModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'plantnet',
|
||||||
|
loadChildren: () => import('./plantnet.module').then( m => m.PlantnetPageModule)
|
||||||
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -1,29 +1,24 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { IonicModule } from '@ionic/angular';
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
import { PlantnetPageRoutingModule } from './plantnet-routing.module';
|
import { PlantnetPageRoutingModule } from './plantnet-routing.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { PlantnetPage } from './plantnet.page';
|
import { PlantnetPage } from './plantnet.page';
|
||||||
|
import { PreviewPage } from './preview/preview.page';
|
||||||
|
import { PreviewPageModule } from "./preview/preview.module"
|
||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { MenuController } from '@ionic/angular';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { Geolocation, PositionOptions } from '@capacitor/geolocation';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
IonicModule,
|
IonicModule,
|
||||||
PlantnetPageRoutingModule
|
PlantnetPageRoutingModule,
|
||||||
|
TranslateModule,
|
||||||
|
PreviewPageModule
|
||||||
|
|
||||||
],
|
],
|
||||||
declarations: [PlantnetPage]
|
declarations: [PlantnetPage],
|
||||||
|
|
||||||
})
|
})
|
||||||
export class PlantnetPageModule {}
|
export class PlantnetPageModule {}
|
||||||
|
|||||||
@@ -1,15 +1,32 @@
|
|||||||
<ion-header>
|
<ion-header [translucent]="true">
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar>
|
||||||
<ion-title>New PlantNet observation</ion-title>
|
|
||||||
|
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-menu-button slot="start"></ion-menu-button>
|
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
|
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
Take a picture
|
||||||
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content [fullscreen]="false">
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>Welcome to the Plantnet API</ion-card-header>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<div *ngIf="image">
|
||||||
|
<img [src]="image" alt="" srcset="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="uploadSuccess">
|
||||||
|
<ion-button expand="block" routerLink="classify" routerDirection="forward">
|
||||||
|
Lets classify this image!
|
||||||
|
</ion-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ion-button (click)="openCamera()" color="primary" expand="block" fill="solid" size="default">
|
||||||
|
Take a picture
|
||||||
|
</ion-button>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { IonicModule } from '@ionic/angular';
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
import { PlantnetPage } from './plantnet.page';
|
import { IspexPage } from './plantnet.page';
|
||||||
|
|
||||||
describe('PlantnetPage', () => {
|
describe('PlantnetPage', () => {
|
||||||
let component: PlantnetPage;
|
let component: IspexPage;
|
||||||
let fixture: ComponentFixture<PlantnetPage>;
|
let fixture: ComponentFixture<IspexPage>;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ PlantnetPage ],
|
declarations: [ IspexPage ],
|
||||||
imports: [IonicModule.forRoot()]
|
imports: [IonicModule.forRoot()]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(PlantnetPage);
|
fixture = TestBed.createComponent(IspexPage);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { HttpClient} from '@angular/common/http';
|
|
||||||
|
|
||||||
|
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||||
|
import { ModalController } from '@ionic/angular';
|
||||||
|
import { PreviewPage } from './preview/preview.page';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { Storage } from '@ionic/storage-angular';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
|
||||||
|
let uploadSuccess = false;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-plantnet',
|
selector: 'app-plantnet',
|
||||||
@@ -8,24 +17,58 @@ import { HttpClient} from '@angular/common/http';
|
|||||||
styleUrls: ['./plantnet.page.scss'],
|
styleUrls: ['./plantnet.page.scss'],
|
||||||
})
|
})
|
||||||
export class PlantnetPage implements OnInit {
|
export class PlantnetPage implements OnInit {
|
||||||
|
uploadSuccess = false; // Add this line
|
||||||
|
image = '';
|
||||||
|
user = null;
|
||||||
|
language = '';
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
altitude: number;
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
constructor(
|
||||||
|
private modal: ModalController,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
|
||||||
|
private storage: Storage,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
// now
|
|
||||||
const data = {
|
|
||||||
PROJECT: 'all',
|
|
||||||
API_URL : 'https://my-api.plantnet.org/v2/identify/all?api-key=',
|
|
||||||
API_KEY : 'my-api-key',
|
|
||||||
API_SIMSEARCH_OPTION : '&include-related-images=true',
|
|
||||||
API_LANG : '&lang=en'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.http.post('http://your-api-endpoint.com', data).subscribe((response) => {
|
|
||||||
console.log(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async openCamera() {
|
||||||
|
const modal = await this.modal.create({
|
||||||
|
component: PreviewPage,
|
||||||
|
cssClass: 'fullscreen',
|
||||||
|
animated: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.onDidDismiss().then(async (data) => {
|
||||||
|
if (data !== null) {
|
||||||
|
this.image = data.data;
|
||||||
|
|
||||||
|
this.uploadSuccess = true;
|
||||||
|
// create parse class
|
||||||
|
|
||||||
|
|
||||||
|
// store image in local storage
|
||||||
|
await this.storage.set('plantnet_image', this.image);
|
||||||
|
} else {
|
||||||
|
console.log('no data');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return await modal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
// navigate to the classify page
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { PreviewPage } from './preview.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: PreviewPage
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class PreviewPageRoutingModule {}
|
||||||
20
src/app/mobisplugins/plantnet/preview/preview.module.ts
Normal file
20
src/app/mobisplugins/plantnet/preview/preview.module.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { PreviewPageRoutingModule } from './preview-routing.module';
|
||||||
|
|
||||||
|
import { PreviewPage } from './preview.page';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
IonicModule,
|
||||||
|
PreviewPageRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [PreviewPage]
|
||||||
|
})
|
||||||
|
export class PreviewPageModule {}
|
||||||
13
src/app/mobisplugins/plantnet/preview/preview.page.html
Normal file
13
src/app/mobisplugins/plantnet/preview/preview.page.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<ion-content id="content" [fullscreen]="true">
|
||||||
|
<div *ngIf="cameraActive">
|
||||||
|
<ion-button (click)="stopCamera()" expand="block" id="close">
|
||||||
|
<ion-icon slot="icon-only" name="close-circle"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
|
||||||
|
<ion-button (click)="takePicture()" expand="block" id="capture">
|
||||||
|
<ion-icon slot="icon-only" name="camera"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
42
src/app/mobisplugins/plantnet/preview/preview.page.scss
Normal file
42
src/app/mobisplugins/plantnet/preview/preview.page.scss
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
ion-content {
|
||||||
|
--background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#capture {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: calc(50% - 25px);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flip {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: calc(50% + 125px);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#close {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: calc(50% - 175px);
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#capture::part(native) {
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#close::part(native) {
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flip::part(native) {
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
24
src/app/mobisplugins/plantnet/preview/preview.page.spec.ts
Normal file
24
src/app/mobisplugins/plantnet/preview/preview.page.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { PreviewPage } from './preview.page';
|
||||||
|
|
||||||
|
describe('PreviewPage', () => {
|
||||||
|
let component: PreviewPage;
|
||||||
|
let fixture: ComponentFixture<PreviewPage>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ PreviewPage ],
|
||||||
|
imports: [IonicModule.forRoot()]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PreviewPage);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
56
src/app/mobisplugins/plantnet/preview/preview.page.ts
Normal file
56
src/app/mobisplugins/plantnet/preview/preview.page.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Plugins } from "@capacitor/core"
|
||||||
|
const { CameraPreview } = Plugins;
|
||||||
|
import { CameraPreviewOptions, CameraPreviewPictureOptions } from '@capacitor-community/camera-preview';
|
||||||
|
import '@capacitor-community/camera-preview';
|
||||||
|
import { ModalController } from '@ionic/angular';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-preview',
|
||||||
|
templateUrl: './preview.page.html',
|
||||||
|
styleUrls: ['./preview.page.scss'],
|
||||||
|
})
|
||||||
|
export class PreviewPage implements OnInit {
|
||||||
|
image ="";
|
||||||
|
cameraActive = false;
|
||||||
|
constructor(private modal: ModalController) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.launchCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
launchCamera() {
|
||||||
|
const cameraPreviewOptions: CameraPreviewOptions = {
|
||||||
|
position: 'rear', // front or rear
|
||||||
|
parent: 'content', // the id on the ion-content
|
||||||
|
className: '',
|
||||||
|
width: window.screen.width, //width of the camera display
|
||||||
|
height: window.screen.height - 200, //height of the camera
|
||||||
|
toBack: false,
|
||||||
|
disableAudio: true,
|
||||||
|
enableHighResolution: true,
|
||||||
|
};
|
||||||
|
CameraPreview['start'](cameraPreviewOptions);
|
||||||
|
this.cameraActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async takePicture() {
|
||||||
|
const cameraPreviewPictureOptions: CameraPreviewPictureOptions = {
|
||||||
|
quality: 90
|
||||||
|
};
|
||||||
|
const result = await CameraPreview['capture'](cameraPreviewPictureOptions);
|
||||||
|
this.image = `data:image/jpeg;base64,${result.value}`;
|
||||||
|
this.stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopCamera() {
|
||||||
|
await CameraPreview['stop']();
|
||||||
|
this.modal.dismiss(this.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
async flipCamera() {
|
||||||
|
await CameraPreview['flip']();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<ion-header class="ion-no-border">
|
<ion-header class="ion-no-border">
|
||||||
<ion-toolbar color="primary">
|
<ion-toolbar color="black">
|
||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
|
<ion-back-button defaultHref="/" icon="chevron-back-outline"></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title class="ion-text-center"> {{ "optionsPage.title" | translate }}</ion-title>
|
<ion-title> {{ "optionsPage.title" | translate }}</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<ion-menu-button></ion-menu-button>
|
<ion-menu-button></ion-menu-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-card-content>
|
<ion-card-content>
|
||||||
<ion-list>
|
<ion-list class="ion-no-padding">
|
||||||
<ion-item color="secondary">{{ "optionsPage.general-settings-label" | translate }}</ion-item>
|
<ion-item color="tertiary">{{ "optionsPage.general-settings-label" | translate }}</ion-item>
|
||||||
<ion-item><ion-label class="bold">{{ "optionsPage.language-label" | translate }} </ion-label>
|
<ion-item><ion-label class="bold">{{ "optionsPage.language-label" | translate }} </ion-label>
|
||||||
<ion-select value="en" [(ngModel)]="language" (ionChange)="setLanguage()" [placeholder]="'optionsPage.languageSelectPlaceholder' | translate">
|
<ion-select value="en" [(ngModel)]="language" (ionChange)="setLanguage()" [placeholder]="'optionsPage.languageSelectPlaceholder' | translate">
|
||||||
<ion-select-option value="en">English</ion-select-option>
|
<ion-select-option value="en">English</ion-select-option>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
ion-item {
|
||||||
|
font-size: small;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-select{
|
||||||
|
max-width: 70% !important;
|
||||||
|
width: 60% !important;
|
||||||
|
padding-left: 20px !important;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,13 +63,10 @@ export class AuthService {
|
|||||||
async sha256(message) {
|
async sha256(message) {
|
||||||
// encode as UTF-8
|
// encode as UTF-8
|
||||||
const msgBuffer = new TextEncoder().encode(message);
|
const msgBuffer = new TextEncoder().encode(message);
|
||||||
|
|
||||||
// hash the message
|
// hash the message
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||||
|
|
||||||
// convert ArrayBuffer to Array
|
// convert ArrayBuffer to Array
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
|
|
||||||
// convert bytes to hex string
|
// convert bytes to hex string
|
||||||
const hashHex = hashArray
|
const hashHex = hashArray
|
||||||
.map((b) => b.toString(16).padStart(2, '0'))
|
.map((b) => b.toString(16).padStart(2, '0'))
|
||||||
@@ -84,7 +81,7 @@ export class AuthService {
|
|||||||
this.platform.ready().then(() => {
|
this.platform.ready().then(() => {
|
||||||
GoogleAuth.initialize({
|
GoogleAuth.initialize({
|
||||||
clientId:
|
clientId:
|
||||||
'553589883639-2h19rvk5ki52j7h0ptjmmlh5keetm3kj.apps.googleusercontent.com', //Test new project MOBIS AUTH WEB
|
'1090658128897-uqpp3egk2v7d0errt8crl220e6tltblq.apps.googleusercontent.com',
|
||||||
scopes: ['profile', 'email'],
|
scopes: ['profile', 'email'],
|
||||||
grantOfflineAccess: false,
|
grantOfflineAccess: false,
|
||||||
});
|
});
|
||||||
@@ -103,7 +100,6 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
await this.sendVerificationMail();
|
await this.sendVerificationMail();
|
||||||
await this.updateUserName(name);
|
await this.updateUserName(name);
|
||||||
// this.updateUserName(name);
|
|
||||||
return loggedInUser;
|
return loggedInUser;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
@@ -128,6 +124,11 @@ export class AuthService {
|
|||||||
await updateProfile(this.afAuth.currentUser, { displayName: name });
|
await updateProfile(this.afAuth.currentUser, { displayName: name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePhotoURL(photoURL: string) {
|
||||||
|
await updateProfile(this.afAuth.currentUser, { photoURL });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async sendPasswordResetEmail(email) {
|
async sendPasswordResetEmail(email) {
|
||||||
console.log('resetting password for email', email);
|
console.log('resetting password for email', email);
|
||||||
try {
|
try {
|
||||||
@@ -212,9 +213,7 @@ export class AuthService {
|
|||||||
console.log('Apple Native login');
|
console.log('Apple Native login');
|
||||||
let loggedInUser = null;
|
let loggedInUser = null;
|
||||||
const nonce = this.nonceService.generateNonce();
|
const nonce = this.nonceService.generateNonce();
|
||||||
|
|
||||||
const hashedNonceHex = await this.sha256(nonce); // see next function
|
const hashedNonceHex = await this.sha256(nonce); // see next function
|
||||||
|
|
||||||
const options: SignInWithAppleOptions = {
|
const options: SignInWithAppleOptions = {
|
||||||
clientId: 'nl.ddq.blackholefinder',
|
clientId: 'nl.ddq.blackholefinder',
|
||||||
redirectURI: 'https://www.ddq.nl',
|
redirectURI: 'https://www.ddq.nl',
|
||||||
@@ -222,11 +221,9 @@ export class AuthService {
|
|||||||
state: '123456',
|
state: '123456',
|
||||||
nonce: hashedNonceHex,
|
nonce: hashedNonceHex,
|
||||||
};
|
};
|
||||||
|
|
||||||
const appleUser: SignInWithAppleResponse = await SignInWithApple.authorize(options);
|
const appleUser: SignInWithAppleResponse = await SignInWithApple.authorize(options);
|
||||||
const provider = new OAuthProvider('apple.com');
|
const provider = new OAuthProvider('apple.com');
|
||||||
const credential = provider.credential({idToken: appleUser.response.identityToken, rawNonce: nonce});
|
const credential = provider.credential({idToken: appleUser.response.identityToken, rawNonce: nonce});
|
||||||
|
|
||||||
await signInWithCredential(this.afAuth, credential).then(
|
await signInWithCredential(this.afAuth, credential).then(
|
||||||
(signedInUser) => {
|
(signedInUser) => {
|
||||||
loggedInUser = signedInUser;
|
loggedInUser = signedInUser;
|
||||||
@@ -238,4 +235,8 @@ export class AuthService {
|
|||||||
logout() {
|
logout() {
|
||||||
return signOut(this.afAuth);
|
return signOut(this.afAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteAccount(){
|
||||||
|
return this.afAuth.currentUser.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Auth } from '@angular/fire/auth';
|
import { Auth, updateProfile } from '@angular/fire/auth';
|
||||||
import { doc, docData, Firestore, setDoc } from '@angular/fire/firestore';
|
import { deleteField, doc, docData, Firestore, setDoc, updateDoc } from '@angular/fire/firestore';
|
||||||
import { ref, uploadString, getDownloadURL, Storage } from '@angular/fire/storage';
|
import { ref, uploadString, getDownloadURL, Storage, deleteObject } from '@angular/fire/storage';
|
||||||
import { Photo } from '@capacitor/camera';
|
import { Photo } from '@capacitor/camera';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -9,17 +9,33 @@ import { Photo } from '@capacitor/camera';
|
|||||||
})
|
})
|
||||||
export class AvatarService {
|
export class AvatarService {
|
||||||
|
|
||||||
constructor(private auth: Auth, private firestore: Firestore, private storage: Storage) { }
|
constructor(private afAuth: Auth, private firestore: Firestore, private storage: Storage) { }
|
||||||
|
|
||||||
getUserProfile() {
|
getAvatar() {
|
||||||
const user = this.auth.currentUser;
|
const user = this.afAuth.currentUser;
|
||||||
const userDocRef = doc(this.firestore, `users/${user.uid}`);
|
const userDocRef = doc(this.firestore, `users/${user.uid}`);
|
||||||
const result = docData(userDocRef, { idField: 'id' });
|
const result = docData(userDocRef, { idField: 'id' });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async updatePhotoURL(photoURL: string) {
|
||||||
|
await updateProfile(this.afAuth.currentUser, { photoURL });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeImage(){
|
||||||
|
const user = this.afAuth.currentUser;
|
||||||
|
//delete the reference to the image in Firestore Database
|
||||||
|
const userDocRef = doc(this.firestore, `users/${user.uid}`);
|
||||||
|
updateDoc(userDocRef, { imageUrl: deleteField() });
|
||||||
|
//delete the actual image in Storage
|
||||||
|
const path = `uploads/${user.uid}/profile.png`;
|
||||||
|
const storageRef = ref(this.storage, path);
|
||||||
|
deleteObject(storageRef);
|
||||||
|
}
|
||||||
|
|
||||||
async uploadImage(cameraFile: Photo) {
|
async uploadImage(cameraFile: Photo) {
|
||||||
const user = this.auth.currentUser;
|
const user = this.afAuth.currentUser;
|
||||||
//console.log(user);
|
//console.log(user);
|
||||||
const path = `uploads/${user.uid}/profile.png`;
|
const path = `uploads/${user.uid}/profile.png`;
|
||||||
const storageRef = ref(this.storage, path);
|
const storageRef = ref(this.storage, path);
|
||||||
@@ -31,8 +47,10 @@ export class AvatarService {
|
|||||||
|
|
||||||
const userDocRef = doc(this.firestore, `users/${user.uid}`);
|
const userDocRef = doc(this.firestore, `users/${user.uid}`);
|
||||||
await setDoc(userDocRef, {
|
await setDoc(userDocRef, {
|
||||||
imageUrl,
|
imageUrl
|
||||||
});
|
});
|
||||||
|
console.log(imageUrl);
|
||||||
|
await this.updatePhotoURL(imageUrl);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
115
src/app/services/weather.service.ts
Normal file
115
src/app/services/weather.service.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
export interface WeatherData{
|
||||||
|
temp: number;
|
||||||
|
description: string;
|
||||||
|
iconUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class WeatherService {
|
||||||
|
public blackGemCoords: {lat: number; lng: number} = {lat: -29.257674, lng: -70.737855};
|
||||||
|
public meerLichtCoords: {lat: number; lng: number} = {lat: -32.379864, lng: 20.811234};
|
||||||
|
|
||||||
|
userLat: number;
|
||||||
|
userLng: number;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
//function that gets the users current location and then calls the showWeather function
|
||||||
|
showCurrentWeather() {
|
||||||
|
this.getLocation().then((pos: {lat: number; lng: number}) => {
|
||||||
|
console.log(`Positon: ${pos.lat} ${pos.lng}`);
|
||||||
|
this.userLat = pos.lat;
|
||||||
|
this.userLng = pos.lng;
|
||||||
|
this.showWeather(pos.lat, pos.lng);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//function that returns the users current location as a promise from the geolocation API
|
||||||
|
getLocation() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
resp => {
|
||||||
|
resolve({ lat: resp.coords.latitude, lng: resp.coords.longitude });
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//function that writes all weather info from the result of the openweathermap.org API in a nice pop up window
|
||||||
|
showWeather(lat: number, lng: number) {
|
||||||
|
this.http
|
||||||
|
.get(
|
||||||
|
'https://api.openweathermap.org/data/2.5/weather?lat=' +
|
||||||
|
lat +
|
||||||
|
'&lon=' +
|
||||||
|
lng +
|
||||||
|
'&appid=edd7d6eab104383027cd6cc21f32d772&units=metric'
|
||||||
|
)
|
||||||
|
.subscribe((res: any) => {
|
||||||
|
const temp = res.main.temp;
|
||||||
|
const iconcode = res.weather[0].icon;
|
||||||
|
console.log(iconcode);
|
||||||
|
const iconurl = 'http://openweathermap.org/img/w/' + iconcode + '.png';
|
||||||
|
console.log(temp);
|
||||||
|
console.log(iconurl);
|
||||||
|
//display the weather icon, fetches the temperature from the weather.main object
|
||||||
|
const weatherPopup = window.open(
|
||||||
|
'',
|
||||||
|
'Weather',
|
||||||
|
'width=300,height=300'
|
||||||
|
);
|
||||||
|
weatherPopup.document.write(
|
||||||
|
'<p>Current weather: ' +
|
||||||
|
res.weather[0].description +
|
||||||
|
'</p><img src=' +
|
||||||
|
iconurl +
|
||||||
|
'><p>Temperature: ' +
|
||||||
|
temp +
|
||||||
|
'°C</p>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//function that returns the weather from openweathermap.org API and returns a single line of text with all weather info as an observable
|
||||||
|
getWeather(lat: number, lng: number): Promise<WeatherData> {
|
||||||
|
let description: string;
|
||||||
|
let temp: number;
|
||||||
|
let iconUrl: string;
|
||||||
|
return new Promise<WeatherData>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
this.http
|
||||||
|
.get(
|
||||||
|
'https://api.openweathermap.org/data/2.5/weather?lat=' +
|
||||||
|
lat +
|
||||||
|
'&lon=' +
|
||||||
|
lng +
|
||||||
|
'&appid=edd7d6eab104383027cd6cc21f32d772&units=metric'
|
||||||
|
).subscribe((res: any) => {
|
||||||
|
temp = res.main.temp;
|
||||||
|
const iconcode = res.weather[0].icon;
|
||||||
|
description = res.weather[0].description;
|
||||||
|
//console.log(iconcode);
|
||||||
|
iconUrl = 'http://openweathermap.org/img/w/' + iconcode + '.png';
|
||||||
|
console.log(temp);
|
||||||
|
console.log(iconUrl);
|
||||||
|
console.log(description);
|
||||||
|
resolve({temp, description, iconUrl});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user