Initial commit

This commit is contained in:
Norbert Schmidt
2023-01-02 09:30:17 +01:00
parent ef89af1dda
commit 3b3353fff1
316 changed files with 7522 additions and 1 deletions

70
.gitignore vendored Normal file
View File

@@ -0,0 +1,70 @@
# Specifies intentionally untracked files to ignore when using Git
# http://git-scm.com/docs/gitignore
*~
*.sw[mnpcod]
.tmp
*.tmp
*.tmp.*
*.sublime-project
*.sublime-workspace
.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate
$RECYCLE.BIN/
*.log
log.txt
npm-debug.log*
/.angular
/.idea
/.ionic
/.sass-cache
/.sourcemaps
/.versions
/.vscode
/coverage
/dist
/node_modules
/platforms
/plugins
/www
# Firebase
.firebase
*-debug.log
.runtimeconfig.json
src/app/app.constant.ts
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.vscode
.idea
/src/app/firebase.json
/src/environments/environment.prod.ts
/src/environments/environment.ts
/src/app/app.constant.ts
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Optional eslint cache
.eslintcache

View File

@@ -1,4 +1,4 @@
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>

14
MainActivity.java Normal file
View File

@@ -0,0 +1,14 @@
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);
}
}

86
README.md Normal file
View File

@@ -0,0 +1,86 @@
# MOBIS_COS4CLOUD
![monocle_logo](https://cos4cloud-eosc.eu/wp-content/uploads/2020/07/logo-cos4cloud-middle.png)
# MOBIS FRONT END SUPPORT PACKAGE
# What is MOBIS?
## Introduction
Mobis is a citizen science platform that enables app developers and researchers to incorporate air and water quality monitoring, as well as biodiversity research, into their own apps. The software includes example integrations with Canair.io, mini-secchi, iSPEX and Plant*Net, which are tools for measuring air and water quality and studying biodiversity, respectively.
Mobis also includes features for user login and storage on the European Open Science Cloud. This makes it easy for researchers and app developers to collect and share data for their projects.
# About this repository
Here you will find software and documentation for our low cost sensors (iSPEX, kDUINO, Fresh Water Watch)
The software is developed using the IONIC framework, Parse Server (Back End).
For iSPEX(r) we offer a basic interface. The iSPEX (r) professional software is using native closed source software and developed outside the EU research project.
Mobis is an open source citizen science software framework that allows app developers and researchers to easily integrate air and water quality monitoring, as well as biodiversity research, into their own apps.
The sample software includes integrations with Canair.io (for air quality), mini-secchi (for water quality), and Plant*Net (for biodiversity research), as well as user login and storage on the European Open Science Cloud.
This readme acts as a reference for the mobis software and / or documentation. Both software and documentation are still into development, so the contents of this file/wiki can and will change over time.
## Audience
Scientists, (app) developers, project partners, people who are lookikng for low cost sensor integration solutions.
## Generic
Provided functionalities in R 1.0:
User login (Apple/Google/Email)
Language switcher
Data sync/upload
WIP:
Full offline support (Parse Server)
Push notifications
# Plugin description
### General description
## CANAIR.IO
### Description
## MINI SECCHI
### Description of the mobis Mini Secchi Plugin
Mini Secchi is a water quality tool that measures the Secchi depth of a lake or river. This is a measure of the water transparency and is used to assess water quality. The Mini Secchi is a low-cost, open-source sensor that can be used to collect water quality data in lakes, rivers and streams.
## iSPEX
### Description of the mobis iSPEX spectropolarimeter Plugin
iSPEX is a spectropolarimeter that measures the optical properties of water or air. It is a low-cost, open-source sensor that can be used to collect water quality data in lakes, rivers and streams.
## Description of the mobis PLANT*NET API plugin
### Description
Plant*Net is a platformfor plant identification
that allows users to identify plants using a smartphone camera.
# BACK END SCRIPTS
Mobile back end. Used for storage, metadata, push notifications, offline sync.
Parse Server
### Parse Server setup
You can run your own (Dockerized) Parse Server or use the Cos4Cloud instance.
If you want to run your own instance, please check the [Parse Server documentation](https://docs.parseplatform.org/parse-server/guide/).
If you want to use the Cos4Cloud instance, please contact us via the [Cos4Cloud Slack channel](https://join.slack.com/t/cos4cloud/shared_invite/zt-8q3vzq1p-7b3hGdYk8jvRyRJd1Nn9gg).
### Sensor Things API 1.1 support
The Cos4Cloud instance of Parse Server supports the Sensor Things API 1.1.
Please check the [Sensor Things API 1.1 documentation](https://developers.sensorup.com/docs/#section/Introduction) for more information.
# FUNDING
![EU_logo](https://cos4cloud-eosc.eu/wp-content/uploads/2020/07/logo-eu.png)
This project has received funding from the European Unions Horizon 2020 research and innovation programme under grant agreement No 863463

178
angular.json Normal file
View File

@@ -0,0 +1,178 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"app": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "www",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
},
{
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
}
],
"styles": [
"src/theme/variables.scss",
"src/global.scss",
"src/styles.css",
"./node_modules/leaflet/dist/leaflet.css",
"node_modules/leaflet.markercluster/dist/MarkerCluster.css",
"node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
},
"ci": {
"progress": false
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "app:build"
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
},
"ci": {
"progress": false
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "app:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"styles": [],
"scripts": [],
"assets": [
{
"glob": "favicon.ico",
"input": "src/",
"output": "/"
},
{
"glob": "**/*",
"input": "src/assets",
"output": "/assets"
}
]
},
"configurations": {
"ci": {
"progress": false,
"watch": false
}
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "app:serve"
},
"configurations": {
"production": {
"devServerTarget": "app:serve:production"
},
"ci": {
"devServerTarget": "app:serve:ci"
}
}
},
"deploy": {
"builder": "@angular/fire:deploy",
"options": {
"prerender": false,
"ssr": false,
"browserTarget": "app:build:production",
"firebaseProject": "mobis-auth",
"firebaseHostingSite": "mobis-auth"
}
}
}
}
},
"cli": {
"schematicCollections": [
"@ionic/angular-toolkit"
],
"analytics": false
},
"schematics": {
"@ionic/angular-toolkit:component": {
"styleext": "scss"
},
"@ionic/angular-toolkit:page": {
"styleext": "scss"
}
}
}

15
capacitor.config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"appId": "nl.ddq.mobisapp",
"appName": "mobisapp",
"bundledWebRuntime": false,
"npmClient": "npm",
"webDir": "dist/demo",
"cordova": {},
"plugins": {
"GoogleAuth": {
"scopes": ["profile", "email"],
"clientId": "778522712103-tqkc468qermr7hvbqsmm2p2ueglcmjsi.apps.googleusercontent.com",
"forceCodeForRefreshToken" : true
}
}
}

18
capacitor.config.ts Normal file
View File

@@ -0,0 +1,18 @@
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'nl.ddq.mobisapp',
appName: 'mobisapp',
webDir: 'www',
bundledWebRuntime: false,
plugins: {
// eslint-disable-next-line @typescript-eslint/naming-convention
GoogleAuth: {
scopes: ['profile', 'email'],
androidClientId: '778522712103-tqkc468qermr7hvbqsmm2p2ueglcmjsi.apps.googleusercontent.com',
forceCodeForRefreshToken: true,
}
}
};
export default config;

101
config.xml Normal file
View File

@@ -0,0 +1,101 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="nl.ddq.mobisapp" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>mobisapp</name>
<description>The Mobis demonstration app.</description>
<author email="web@ddq.nl" href="http://ionicframework.com/">DDQ/Pocket science</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<preference name="ScrollEnabled" value="false" />
<preference name="BackupWebStorage" value="none" />
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="FadeSplashScreenDuration" value="300" />
<preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="SplashScreen" value="screen" />
<preference name="SplashScreenDelay" value="3000" />
<platform name="android">
<preference name="Scheme" value="http" />
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application" xmlns:android="http://schemas.android.com/apk/res/android">
<application android:networkSecurityConfig="@xml/network_security_config" />
</edit-config>
<resource-file src="resources/android/xml/network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" />
<allow-intent href="market:*" />
<icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
<icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
<icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
<icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
<icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
<icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
<splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
<splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
<splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
<splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
<splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
<splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
<splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
<splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
<splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
<splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
<splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
<splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<icon height="57" src="resources/ios/icon/icon.png" width="57" />
<icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
<icon height="29" src="resources/ios/icon/icon-small.png" width="29" />
<icon height="58" src="resources/ios/icon/icon-small@2x.png" width="58" />
<icon height="87" src="resources/ios/icon/icon-small@3x.png" width="87" />
<icon height="20" src="resources/ios/icon/icon-20.png" width="20" />
<icon height="40" src="resources/ios/icon/icon-20@2x.png" width="40" />
<icon height="60" src="resources/ios/icon/icon-20@3x.png" width="60" />
<icon height="48" src="resources/ios/icon/icon-24@2x.png" width="48" />
<icon height="55" src="resources/ios/icon/icon-27.5@2x.png" width="55" />
<icon height="29" src="resources/ios/icon/icon-29.png" width="29" />
<icon height="58" src="resources/ios/icon/icon-29@2x.png" width="58" />
<icon height="87" src="resources/ios/icon/icon-29@3x.png" width="87" />
<icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
<icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
<icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
<icon height="88" src="resources/ios/icon/icon-44@2x.png" width="88" />
<icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
<icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
<icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
<icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
<icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
<icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
<icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
<icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
<icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
<icon height="167" src="resources/ios/icon/icon-83.5@2x.png" width="167" />
<icon height="172" src="resources/ios/icon/icon-86@2x.png" width="172" />
<icon height="196" src="resources/ios/icon/icon-98@2x.png" width="196" />
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
<splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
<splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
<splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
<splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
<splash height="1125" src="resources/ios/splash/Default-Landscape-2436h.png" width="2436" />
<splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
<splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
<splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
<splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048" />
<splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732" />
<splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
<splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
<splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
<splash height="2436" src="resources/ios/splash/Default-2436h.png" width="1125" />
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
</platform>
<plugin name="cordova-plugin-statusbar" spec="2.4.2" />
<plugin name="cordova-plugin-device" spec="2.0.2" />
<plugin name="cordova-plugin-splashscreen" spec="5.0.2" />
<plugin name="cordova-plugin-ionic-webview" spec="^5.0.0" />
<plugin name="cordova-plugin-ionic-keyboard" spec="^2.0.5" />
</widget>

37
e2e/protractor.conf.js Normal file
View File

@@ -0,0 +1,37 @@
// @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
}
}));
}
};

14
e2e/src/app.e2e-spec.ts Normal file
View File

@@ -0,0 +1,14 @@
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');
});
});

11
e2e/src/app.po.ts Normal file
View File

@@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.deepCss('app-root ion-content')).getText();
}
}

12
e2e/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"node"
]
}
}

8
ionic.config.json Normal file
View File

@@ -0,0 +1,8 @@
{
"name": "mobisapp",
"integrations": {
"capacitor": {},
"cordova": {}
},
"type": "angular"
}

44
karma.conf.js Normal file
View File

@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/ngv'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

100
package.json Normal file
View File

@@ -0,0 +1,100 @@
{
"name": "firebase-login",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/fire": "^7.4.1",
"@angular/forms": "^14.0.0",
"@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0",
"@awesome-cordova-plugins/background-mode": "^6.2.0",
"@awesome-cordova-plugins/core": "6.2.0",
"@capacitor-community/apple-sign-in": "^4.0.0",
"@capacitor-community/bluetooth-le": "^2.0.1",
"@capacitor-community/camera-preview": "^4.0.0",
"@capacitor/android": "4.0.1",
"@capacitor/app": "4.0.1",
"@capacitor/camera": "^4.0.1",
"@capacitor/core": "4.0.1",
"@capacitor/device": "4.1.0",
"@capacitor/geolocation": "^4.0.1",
"@capacitor/haptics": "4.0.1",
"@capacitor/ios": "4.0.1",
"@capacitor/keyboard": "4.0.1",
"@capacitor/status-bar": "4.0.1",
"@codetrix-studio/capacitor-google-auth": "^3.2.0",
"@ionic-native/vibration": "^5.36.0",
"@ionic/angular": "^6.1.9",
"@ionic/pwa-elements": "^3.1.1",
"@ionic/storage-angular": "^3.0.6",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@types/leaflet.markercluster": "^1.5.1",
"@types/parse": "^2.18.18",
"cordova-plugin-background-mode": "^0.7.3",
"cordova-plugin-device": "2.1.0",
"guid-typescript": "^1.0.9",
"leaflet": "^1.9.2",
"leaflet.markercluster": "^1.5.3",
"ng-apexcharts": "^1.7.1",
"parse": "^3.5.1",
"rxjs": "^7.5.7",
"stream": "^0.0.2",
"timers": "^0.1.1",
"tslib": "^2.2.0",
"xml2js": "^0.4.23",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.0.0",
"@angular-eslint/builder": "~13.0.1",
"@angular-eslint/eslint-plugin": "~13.0.1",
"@angular-eslint/eslint-plugin-template": "~13.0.1",
"@angular-eslint/template-parser": "~13.0.1",
"@angular/cli": "^14.0.0",
"@angular/compiler": "^14.0.0",
"@angular/compiler-cli": "^14.0.0",
"@angular/language-service": "^14.0.0",
"@capacitor/cli": "4.0.1",
"@ionic/angular-toolkit": "^6.0.0",
"@ionic/cli": "6.20.4",
"@ionic/lab": "3.2.15",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/leaflet": "^1.9.0",
"@types/node": "^12.11.1",
"@types/xml2js": "^0.4.11",
"@typescript-eslint/eslint-plugin": "5.3.0",
"@typescript-eslint/parser": "5.3.0",
"cordova-res": "0.15.4",
"eslint": "^7.6.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsdoc": "30.7.6",
"eslint-plugin-prefer-arrow": "1.2.2",
"jasmine-core": "~3.8.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.2",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"typescript": "~4.7.3"
},
"description": "An Ionic project"
}

93
project-summary.md Normal file
View File

@@ -0,0 +1,93 @@
## Capacitor Plugins
### @capacitor-community
- **apple-sign-in@4.0.0**
### @capacitor
- **app@4.0.1** - (Latest 4.1.1)
- **camera@4.1.3** - (Latest 4.1.4)
- **geolocation@4.0.1** - (Latest 4.1.0)
- **haptics@4.0.1** - (Latest 4.1.0)
- **keyboard@4.0.1** - (Latest 4.1.0)
- **status-bar@4.0.1** - (Latest 4.1.1)
### @codetrix-studio
- **capacitor-google-auth@3.2.0**
## Cordova Plugins
## Dependencies
### @angular-devkit
- **build-angular@14.2.6** - (Latest 15.0.2)
### @angular-eslint
- **builder@13.0.1** - (Latest 15.1.0)
- **eslint-plugin@13.0.1** - (Latest 15.1.0)
- **eslint-plugin-template@13.0.1** - (Latest 15.1.0)
- **template-parser@13.0.1** - (Latest 15.1.0)
### @angular
- **cli@14.2.6** - (Latest 15.0.2)
- **common@14.2.7** - (Latest 15.0.2)
- **compiler@14.2.7** - (Latest 15.0.2)
- **compiler-cli@14.2.7** - (Latest 15.0.2)
- **core@14.2.7** - (Latest 15.0.2)
- **fire@7.4.1** - (Latest 7.5.0)
- **forms@14.2.7** - (Latest 15.0.2)
- **language-service@14.2.7** - (Latest 15.0.2)
- **platform-browser@14.2.7** - (Latest 15.0.2)
- **platform-browser-dynamic@14.2.7** - (Latest 15.0.2)
- **router@14.2.7** - (Latest 15.0.2)
### @capacitor
- **android@4.0.1** - (Latest 4.6.1)
- **cli@4.0.1** - (Latest 4.6.1)
- **core@4.0.1** - (Latest 4.6.1)
- **ios@4.0.1** - (Latest 4.6.1)
### @ionic
- **angular@6.3.2** - (Latest 6.3.9)
- **angular-toolkit@6.1.0** - (Latest 7.0.0)
- **cli@6.20.4**
- **pwa-elements@3.1.1**
- **storage-angular@3.0.6**
### @ngx-translate
- **core@14.0.0**
- **http-loader@7.0.0**
### @types
- **jasmine@3.6.11** - (Latest 4.3.1)
- **jasminewd2@2.0.10**
- **leaflet@1.9.0**
- **leaflet.markercluster@1.5.1**
- **node@12.20.55** - (Latest 18.11.11)
- **parse@2.18.18** - (Latest 3.0.1)
- **xml2js@0.4.11**
### @typescript-eslint
- **eslint-plugin@5.3.0** - (Latest 5.45.1)
- **parser@5.3.0** - (Latest 5.45.1)
### Other Dependencies
- **cordova-res@0.15.4**
- **eslint@7.32.0** - (Latest 8.29.0)
- **eslint-plugin-import@2.22.1** - (Latest 2.26.0)
- **eslint-plugin-jsdoc@30.7.6** - (Latest 39.6.4)
- **eslint-plugin-prefer-arrow@1.2.2** - (Latest 1.2.3)
- **jasmine-core@3.8.0** - (Latest 4.5.0)
- **jasmine-spec-reporter@5.0.2** - (Latest 7.0.0)
- **karma@6.3.20** - (Latest 6.4.1)
- **karma-chrome-launcher@3.1.1**
- **karma-coverage@2.0.3** - (Latest 2.2.0)
- **karma-coverage-istanbul-reporter@3.0.3**
- **karma-jasmine@4.0.2** - (Latest 5.1.0)
- **karma-jasmine-html-reporter@1.7.0** - (Latest 2.0.0)
- **leaflet@1.9.2** - (Latest 1.9.3)
- **leaflet.markercluster@1.5.3**
- **ng-apexcharts@1.7.4**
- **parse@3.4.4** - (Latest 3.5.1)
- **protractor@7.0.0**
- **rxjs@7.5.7** - (Latest 7.6.0)
- **stream@0.0.2**
- **timers@0.1.1**
- **ts-node@8.3.0** - (Latest 10.9.1)
- **tslib@2.4.0** - (Latest 2.4.1)
- **typescript@4.7.4** - (Latest 4.9.3)
- **xml2js@0.4.23**
- **zone.js@0.11.8** - (Latest 0.12.0)
## Nonstandard naming
The following files and folders do not follow the standard naming convention:

8
resources/README.md Normal file
View File

@@ -0,0 +1,8 @@
These are Cordova resources. You can replace icon.png and splash.png and run
`ionic cordova resources` to generate custom icons and splash screens for your
app. See `ionic cordova resources --help` for details.
Cordova reference documentation:
- Icons: https://cordova.apache.org/docs/en/latest/config_ref/images.html
- Splash Screens: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
resources/ios/icon/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resources/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -0,0 +1,88 @@
import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import {redirectUnauthorizedTo, redirectLoggedInTo, canActivate } from '@angular/fire/auth-guard';
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']);
const redirectLoggedInToProfile = () => redirectLoggedInTo(['profile']);
const routes: Routes = [
{
path: 'home',
loadChildren: () => import('./home/home.module').then( m => m.HomePageModule),
},
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'login',
loadChildren: () => import('./auth/login/login.module').then( m => m.LoginPageModule),
...canActivate(redirectLoggedInToProfile)
},
{
path: 'resetpw',
loadChildren: () => import('./auth/resetpw/resetpw.module').then( m => m.ResetpwPageModule)
},
{
path: 'profile',
loadChildren: () => import('./auth/profile/profile.module').then( m => m.ProfilePageModule),
...canActivate(redirectUnauthorizedToLogin)
},
{
path: 'signup',
loadChildren: () => import('./auth/signup/signup.module').then( m => m.SignupPageModule)
},
{
path: 'observe',
loadChildren: () => import('./observe/observe.module').then( m => m.MeasurePageModule)
},
{
path: 'options',
loadChildren: () => import('./options/options.module').then( m => m.OptionsPageModule)
},
{
path: 'plantnet',
loadChildren: () => import('./mobisplugins/plantnet/plantnet.module').then( m => m.PlantnetPageModule)
},
{
path: 'ispex',
loadChildren: () => import('./mobisplugins/ispex/ispex.module').then( m => m.IspexPageModule)
},
{
path: 'minisecchi',
loadChildren: () => import('./mobisplugins/minisecchi/minisecchi.module').then( m => m.MinisecchiPageModule)
},
{
path: 'canairiopm25',
loadChildren: () => import('./mobisplugins/canairiopm25/canairiopm25.module').then( m => m.Canairiopm25PageModule)
},
{
path: 'canairioco2',
loadChildren: () => import('./mobisplugins/canairioco2/canairioco2.module').then( m => m.Canairioco2PageModule)
},
{
path: 'ms_measure',
loadChildren: () => import('./mobisplugins/minisecchi/measure/measure.module').then( m => m.MeasurePageModule)
},
{
path: 'ms_instructions',
loadChildren: () => import('./mobisplugins/minisecchi/instructions/instructions.module').then( m => m.InstructionsPageModule)
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -0,0 +1,60 @@
<ion-app>
<ion-menu side="start" contentId="main">
<ion-menu-toggle>
<ion-header color="primary">
<ion-toolbar>
<ion-item lines="none">
<ion-label routerLink="home">M O B I S</ion-label>
<ion-buttons>
<ion-button type="icon-only" (click)="closeMenu()"><ion-icon name="close-outline" color="dark"></ion-icon></ion-button>
</ion-buttons>
</ion-item>
</ion-toolbar>
</ion-header>
</ion-menu-toggle>
<ion-content>
<ion-list>
<ion-list-header>
<ion-label>{{ "menu.account" | translate }}</ion-label>
</ion-list-header>
<ion-menu-toggle>
<ion-item button routerLink="signup" [disabled]="user">
<ion-icon name="person-add-outline"></ion-icon>
<ion-label>{{ "menu.signup" | translate }}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle>
<ion-item button routerLink="login" [disabled]="user">
<ion-icon name="log-in-outline"></ion-icon>
<ion-label>{{ "menu.signin" | translate }}</ion-label>
</ion-item>
</ion-menu-toggle>
<ion-menu-toggle>
<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-menu-toggle>
<ion-menu-toggle>
<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-menu-toggle>
<ion-list-header>
<ion-label>{{ "menu.settings" | translate }}</ion-label>
</ion-list-header>
<ion-menu-toggle>
<ion-item button routerLink="options">
<ion-icon name="settings-outline"></ion-icon>
<ion-label>{{ "menu.options" | translate }}</ion-label>
</ion-item>
</ion-menu-toggle>
</ion-list>
</ion-content>
</ion-menu>
<ion-router-outlet id="main"></ion-router-outlet>
</ion-app>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,23 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
// TODO: add more tests!
});

53
src/app/app.component.ts Normal file
View File

@@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { Auth, user } from '@angular/fire/auth';
import { Router } from '@angular/router';
import { MenuController } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core';
import { AuthService } from './services/auth.service';
import { Storage } from '@ionic/storage-angular';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent implements OnInit{
user = null;
language= '';
constructor(
private menuCtrl: MenuController,
private authService: AuthService,
private router: Router,
private afAuth: Auth,
private translateService: TranslateService,
private storage: Storage
) {
user(this.afAuth).subscribe((response) => {
//fill the user to verify if someone is logged in
this.user = response;
});
}
async ngOnInit() {
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();
//if
}
this.translateService.use(this.language);
}
closeMenu() {
this.menuCtrl.close();
}
async logout() {
await this.authService.logout();
this.router.navigateByUrl('/home', { replaceUrl: true });
}
}

41
src/app/app.module.ts Normal file
View File

@@ -0,0 +1,41 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { environment } from '../environments/environment';
import { provideAuth, getAuth } from '@angular/fire/auth';
import { provideFirestore, getFirestore } from '@angular/fire/firestore';
import { provideStorage, getStorage } from '@angular/fire/storage';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { IonicStorageModule } from '@ionic/storage-angular';
//define loader to load local files(on given path) with HttpClient
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/languages/', '.json');
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule,
IonicStorageModule.forRoot(),
TranslateModule.forRoot({loader: {provide: TranslateLoader, useFactory: (createTranslateLoader), deps: [HttpClient]}}),
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideAuth(() => getAuth()),
provideFirestore(() => getFirestore()),
provideStorage(() => getStorage()),
],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginPage } from './login.page';
const routes: Routes = [
{
path: '',
component: LoginPage
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class LoginPageRoutingModule {}

View File

@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { LoginPageRoutingModule } from './login-routing.module';
import { LoginPage } from './login.page';
import { TranslateModule } from '@ngx-translate/core';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ReactiveFormsModule,
LoginPageRoutingModule,
TranslateModule
],
declarations: [LoginPage]
})
export class LoginPageModule {}

View File

@@ -0,0 +1,52 @@
<ion-header class="ion-no-border">
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="home" icon="chevron-back-outline"></ion-back-button>
</ion-buttons>
<ion-title class="ion-text-center">{{ "loginPage.title" | translate}}</ion-title>
<ion-buttons slot="end">
<ion-menu-button></ion-menu-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card>
<ion-card-content>
<ion-grid>
<ion-row>
<ion-col sizeSm="6" offsetSm="3" sizeLg="4" offsetLg="4" sizeMd="6" offsetMd="3" *ngIf="showForm" class="ion-text-center">
<form (ngSubmit)="login()" [formGroup]="credentials">
<ion-item fill="solid" class="ion-margin-bottom">
<ion-input type="email" [placeholder]="'loginPage.email-placeholder' | translate" formControlName="email"><ion-icon name="mail-outline"></ion-icon></ion-input>
<ion-icon name="person-outline" slot=“end” align-self-center></ion-icon>
<ion-note slot="error" *ngIf="(email.dirty || email.touched) && email.errors">{{ "loginPage.email-invalid" | translate }}</ion-note>
</ion-item>
<ion-item fill="solid" class="ion-margin-bottom">
<ion-input type="password" [placeholder]="'loginPage.password-placeholder' | translate" formControlName="password"><ion-icon name="key-outline"></ion-icon></ion-input>
<ion-note slot="error" *ngIf="(password.dirty || password.touched) && password.errors">{{ "loginPage.password-invalid" | translate }}</ion-note>
</ion-item>
<ion-button type="submit" expand="block" [disabled]="!credentials.valid">{{ "loginPage.login-button" | translate }}</ion-button>
<a [routerLink]="['/resetpw']">{{ "loginPage.forgot-password" | translate }}</a>
</form>
</ion-col>
</ion-row>
<ion-row>
<ion-col class="ion-text-center" sizeSm="6" offsetSm="3" sizeLg="4" offsetLg="4" sizeMd="6" offsetMd="3">
<ion-button type="button" (click)="toggleForm()" expand="block"><ion-icon name="person-outline"></ion-icon>{{ "loginPage.login-username" | translate }}</ion-button>
<ion-button type="button" (click)="anonymousLogin()" expand="block"><ion-icon name="eye-outline"></ion-icon>{{ "loginPage.login-anonymous" | translate }}</ion-button>
<ion-button type="button" (click)="loginWithGoogle()" expand="block" color="danger"><ion-icon name="logo-google"></ion-icon>{{ "loginPage.login-google" | translate }}</ion-button>
<ion-button type="button" (click)="loginWithApple()" expand="block" color="dark"><ion-icon name="logo-apple"></ion-icon>{{ "loginPage.login-apple" | translate }}</ion-button>
<a href="signup">{{ "loginPage.goto-signup" | translate }}</a>
<br>
</ion-col>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</ion-content>

View File

@@ -0,0 +1,11 @@
ion-input ion-icon{
padding-right: 15px;
}
ion-button {
border: 1px solid var(--ion-color-grey);
border-radius: 16px;
--color-hover: var(--ion-color-primary)
}

Some files were not shown because too many files have changed in this diff Show More