Proyecto final de Ionic con Firebase completo.
Aquí adjunto el enlace de mi github para poder descargar el proyecto =
- https://github.com/adriansoriagarcia/proyecto-manitas
Aquí adjunto los enlaces de varios videos de demostracion de la app =
-
https://drive.google.com/file/d/1bZefjPBnlS9rzfKkcRCiWRZGyVtQIIV4/view?usp=sharing
-
https://drive.google.com/file/d/1hEyxsS_uH4i6_mkX-xujs_m3u69Dthr1/view?usp=sharing
Imágenes de funcionamiento del proyecto
Pantalla home sin usuario logueado.
Pantalla home con usuario logueado.
Pantalla de formulario con sus correspondientes botones en el cual se puede añadir, eliminar imágenes, guardar las imagenes seleccionadas y añadir las reparaciones..
Pantalla de información con su botón para llamar y su mapa de localización.
Pantalla de login, en la cual tambien podemos registrarnos pulsando en “Create an account”.
Pantalla de registro, en la cual una vez registrado, nos podemos dirigir a la página de login..
Vista de la página home desde el ordenador.
Vista de la página login desde el ordenador.
Vista de la página registro desde el ordenador.
Vista de la página formulario desde el ordenador.
Archivo Home.html
<ion-header [translucent]="true">
<ion-toolbar>
<ion-title>
Proyecto manitas
</ion-title>
<p class="usuario">User: </p>
<ion-button class="login" (click)="login()" color="dark" *ngIf=" this.userUID == ''">
<ion-icon name="log-in-outline"></ion-icon>
</ion-button>
<ion-button class="login" (click)="logout()" color="dark" *ngIf=" this.userUID != ''">
<ion-icon name="log-out-outline"></ion-icon>
</ion-button>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-searchbar showCancelButton="never" [(ngModel)]="filtro"></ion-searchbar>
<h1>LISTA DE REPARACIONES</h1>
<!--<ion-fab vertical="bottom" horizontal="end" color="primary" slot="fixed">
<ion-fab-button (click)="pasarSegudaPantalla()"><ion-icon name="add-circle-outline"></ion-icon></ion-fab-button>
</ion-fab>-->
<ion-grid class="contenido">
<ion-row>
<ng-container *ngFor="let documentReparacion of arrayColeccionReparaciones">
<ion-col *ngIf="filtro === '' || documentReparacion.data.nombre.includes(filtro)|| documentReparacion.data.fecha.includes(filtro) " size-xl="2" size-lg="2" size-md="4" size-sm="6" size-xs="12">
<ion-card class="tarjetas" (click)="selecReparacion(documentReparacion)">
<ion-card-content>
<h2>Nombre: </h2>
<p>Fecha: </p>
<p>Ubicación: </p>
<p>Precio: €</p>
<img class="imagenes" src="">
</ion-card-content>
</ion-card>
</ion-col>
</ng-container>
</ion-row>
</ion-grid>
<ion-fab vertical="bottom" horizontal="left" slot="fixed">
<ion-fab-button>
<ion-icon name="share"></ion-icon>
</ion-fab-button>
<ion-fab-list side="end">
<ion-fab-button href="https://es-es.facebook.com/"><ion-icon name="logo-facebook"></ion-icon></ion-fab-button>
</ion-fab-list>
<ion-fab-list side="top">
<ion-fab-button href="https://www.instagram.com/?hl=es"><ion-icon name="logo-instagram"></ion-icon></ion-fab-button>
</ion-fab-list>
</ion-fab>
</ion-content>
<ion-toolbar>
<ion-tabs>
<ion-tab-bar >
<ion-tab-button tab="anadir" *ngIf=" this.userUID != ''">
<ion-icon name="add-circle-outline"></ion-icon>
<ion-label>Añadir</ion-label>
</ion-tab-button>
<ion-tab-button tab="acerca">
<ion-icon name="information-circle-outline"></ion-icon>
<ion-label>Acerca de</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
</ion-toolbar>
Archivo Detalle.html
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="pasarPrimeraPantalla()" color="dark">
<ion-icon name="arrow-back-outline"></ion-icon>
</ion-button>
<ion-title>Formulario</ion-title>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-item>
<ion-label>Nombre Cliente </ion-label>
<ion-input [(ngModel)]="document.data.nombre"></ion-input>
</ion-item>
<ion-item>
<ion-label>Fecha prevista reparación</ion-label>
<ion-input type="date" [(ngModel)]="document.data.fecha"></ion-input>
</ion-item>
<ion-item>
<ion-label>Lugar de la reparación</ion-label>
<ion-input [(ngModel)]="document.data.lugar"></ion-input>
</ion-item>
<ion-item>
<ion-label>Precio de la reparación</ion-label>
<ion-input [(ngModel)]="document.data.precio"></ion-input>
</ion-item>
<ion-item>
<ion-label>Imagen</ion-label>
<ion-button color="light" (click)="seleccionarImagen()">
<ion-icon name="add"></ion-icon></ion-button>
<ion-button color="light" (click)="borrarImagen()">
<ion-icon name="close"></ion-icon></ion-button>
<img class="imagenes" src="" *ngIf="this.document.data.imagen != null">
</ion-item>
<ion-button color="light" (click)="guardarDatos()">
<ion-icon name="save-outline"></ion-icon></ion-button>
<ion-button color="light" (click)="clicBotonInsertar()" *ngIf="this.document.id == ''">
<ion-icon name="add-circle-outline"></ion-icon></ion-button>
<ion-list >
<ion-button color="light" (click)="clicBotonModificar()" *ngIf="this.document.id == this.id">
<ion-icon name="layers-outline"></ion-icon></ion-button>
<ion-button color="light" (click)="clicBotonBorrar()" *ngIf="this.document.id == this.id">
<ion-icon name="trash-outline"></ion-icon></ion-button>
</ion-list>
<ion-col size="2">
<ion-button color="light" (click)="ShareGeneric()" *ngIf="this.document.id == this.id">
<ion-icon name="share-social-outline"></ion-icon></ion-button>
</ion-col>
</ion-content>
Archivo Informacion.html
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button (click)="pasarPrimeraPantalla()" color="dark">
<ion-icon name="arrow-back-outline"></ion-icon>
</ion-button>
<ion-title>Información</ion-title>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<h3>Atención al cliente: <ion-button (click)='llamada()'>
<ion-icon name="call-outline"></ion-icon>
</ion-button></h3>
<h3 class="local">Localización: </h3>
<div id="mapId" style="width:100%; height:50%; "></div>
</ion-content>
Archivo Home.ts.
import { Component, ViewChild } from '@angular/core';
import { FirestoreService } from '../firestore.service';
import { Reparacion } from '../reparacion';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { LoadingController } from '@ionic/angular';
import { AngularFireAuth } from '@angular/fire/compat/auth';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
usuario: String = "";
userEmail: String = "";
userUID: String = "";
isLogged: boolean;
reparacionEditando: Reparacion;
filtro: string = '';
arrayColeccionReparaciones: any = [{
id: "",
data: {} as Reparacion
}];
obtenerListaReparaciones(){
this.firestoreService.consultar("reparaciones").subscribe((resultadoConsultaReparaciones) => {
this.arrayColeccionReparaciones = [];
resultadoConsultaReparaciones.forEach((datosReparacion: any) => {
this.arrayColeccionReparaciones.push({
id: datosReparacion.payload.doc.id,
data: datosReparacion.payload.doc.data()
});
})
});
}
constructor(private firestoreService: FirestoreService,
private router:Router,
public loadingCtrl: LoadingController,
private authService: AuthService,
public afAuth: AngularFireAuth) {
// Crear una reparacion vacía
this.obtenerListaReparaciones();
this.reparacionEditando = {} as Reparacion;
}
idReparacionSelec: string;
pasarSegudaPantalla () {
this.router.navigate(['detalle/:id'])
}
selecReparacion(reparacionSelec) {
console.log("Reparacion seleccionada: ");
console.log(reparacionSelec);
this.idReparacionSelec = reparacionSelec.id;
this.reparacionEditando.nombre = reparacionSelec.data.nombre;
this.reparacionEditando.fecha = reparacionSelec.data.fecha;
this.reparacionEditando.lugar = reparacionSelec.data.lugar;
this.reparacionEditando.lugar = reparacionSelec.data.precio;
this.reparacionEditando.imagen = reparacionSelec.data.imagen;
this.router.navigate(['/detalle', this.idReparacionSelec]);
}
ionViewDidEnter() {
this.isLogged = false;
this.afAuth.user.subscribe(user => {
if(user){
this.userEmail = user.email;
var email_analizado = /^([^]+)@(\w+).(\w+)$/.exec(user.email);
this.usuario=email_analizado[1];
console.log(this.usuario)
this.userUID = user.uid;
this.isLogged = true;
} else {
this.router.navigate(["/home"]);
}
})
}
login() {
this.router.navigate(["/login"]);
}
logout(){
this.authService.doLogout()
.then(res => {
this.userEmail = "";
this.userUID = "";
this.usuario="";
this.isLogged = false;
console.log(this.userEmail);
}, err => console.log(err));
}
}
## Archivo Detalle.ts.
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute } from '@angular/router';
import { Reparacion } from '../reparacion';
import { FirestoreService } from '../firestore.service';
import { AlertController } from '@ionic/angular';
import { Router } from '@angular/router';
import { LoadingController, ToastController } from '@ionic/angular';
import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx';
import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx';
//import { SocialSharing } from '@ionic-native/social-sharing/ngx';
import { AngularFireAuth } from '@angular/fire/compat/auth';
@Component({
selector: 'app-detalle',
templateUrl: './detalle.page.html',
styleUrls: ['./detalle.page.scss'],
})
export class DetallePage implements OnInit {
// Imagen que se va a mostrar en la página
imagenTempSrc: String;
subirArchivoImagen: boolean = false;
borrarArchivoImagen: boolean = false;
// Nombre de la colección en Firestore Database
reparacciones: String = "EjemploImagenes";
reparacionEditando: Reparacion;
id:string="";
imageURL: String;
userEmail: String = "";
userUID: String = "";
isLogged: boolean;
document: any = {
id: "",
data: {} as Reparacion,
};
arrayColeccionReparaciones: any = [{
id: "",
data: {} as Reparacion
}];
obtenerListaReparaciones(){
this.firestoreService.consultar("reparaciones").subscribe((resultadoConsultaReparaciones) => {
this.arrayColeccionReparaciones = [];
resultadoConsultaReparaciones.forEach((datosReparacion: any) => {
this.arrayColeccionReparaciones.push({
id: datosReparacion.payload.doc.id,
data: datosReparacion.payload.doc.data()
});
})
});
}
constructor(private activatedRoute: ActivatedRoute,
private firestoreService: FirestoreService,
public alertController: AlertController,
private loadingController: LoadingController,
private toastController: ToastController,
private imagePicker: ImagePicker,
private router:Router,
private socialSharing: SocialSharing,
public afAuth: AngularFireAuth
) {
console.log(this.id)
this.reparacionEditando = {} as Reparacion;
this.obtenerListaReparaciones();
};
pasarPrimeraPantalla () {
this.router.navigate(['home'])
}
clicBotonInsertar() {
this.firestoreService.insertar("reparaciones", this.document.data).then(() => {
console.log('Reparación creada correctamente!');
this.reparacionEditando= {} as Reparacion;
this.pasarPrimeraPantalla();
}, (error) => {
console.error(error);
});
}
ngOnInit() {
this.id = this.activatedRoute.snapshot.paramMap.get('id')
this.firestoreService.consultarPorId("reparaciones", this.id).subscribe((resultado) => {
// Preguntar si se hay encontrado un document con ese ID
if(resultado.payload.data() != null) {
this.document.id = resultado.payload.id
this.document.data = resultado.payload.data();
this.imagenTempSrc = this.document.data.imagen;
// Como ejemplo, mostrar el nombre del cliente en consola
console.log(this.document.data.imagen);
//console.log(this.imageURL)
} else {
// No se ha encontrado un document con ese ID. Vaciar los datos que hubiera
this.document.data = {} as Reparacion;
//console.log(this.document.data.imagen)
}
});
}
clicBotonBorrar() {
this.alertController.create({
header: 'ALERTA',
subHeader: '¡Estas a punto de borrar una reparación!',
message: 'Si deseas borrar una reparacion pulse si, en caso contrario pulse no',
buttons: [
{
text: 'No',
handler: () => {
console.log('nunca');
}
},
{
text: 'Si',
handler: () => {
console.log('si');
this.borrarImagen();
this.guardarDatos();
this.firestoreService.borrar("reparaciones", this.document.id).then(() => {
// Actualizar la lista completa
this.obtenerListaReparaciones();
console.log('Reparación borrada correctamente!');
// Limpiar datos de pantalla
this.reparacionEditando = {} as Reparacion;
this.pasarPrimeraPantalla();
})
}
}
]
}).then(res => {
res.present();
});
}
clicBotonModificar() {
this.firestoreService.actualizar("reparaciones", this.document.id, this.document.data).then(() => {
// Actualizar la lista completa
this.obtenerListaReparaciones();
console.log('Reparación modificada correctamente!');
// Limpiar datos de pantalla
this.reparacionEditando = {} as Reparacion;
this.pasarPrimeraPantalla();
})
}
async seleccionarImagen() {
// Comprobar si la aplicación tiene permisos de lectura
this.imagePicker.hasReadPermission().then(
(result) => {
// Si no tiene permiso de lectura se solicita al usuario
if(result == false){
this.imagePicker.requestReadPermission();
}
else {
// Abrir selector de imágenes (ImagePicker)
this.imagePicker.getPictures({
maximumImagesCount: 1, // Permitir sólo 1 imagen
outputType: 1 // 1 = Base64
}).then(
(results) => { // En la variable results se tienen las imágenes seleccionadas
if(results.length > 0) { // Si el usuario ha elegido alguna imagen
this.imagenTempSrc = "data:image/jpeg;base64,"+results[0];
//this.document.data.imagen=this.imagenTempSrc;
console.log("Imagen que se ha seleccionado (en Base64): " + this.imagenTempSrc);
// Se informa que se ha cambiado para que se suba la imagen cuando se actualice la BD
this.subirArchivoImagen = true;
this.borrarArchivoImagen = false;
}
},
(err) => {
console.log(err)
}
);
}
}, (err) => {
console.log(err);
});
}
public guardarDatos() {
if(this.subirArchivoImagen) {
// Borrar el archivo de la imagen antigua si la hubiera
if(this.document.data.imagen != null) {
this.eliminarArchivo(this.document.data.imagen);
}
// Si la imagen es nueva se sube como archivo y se actualiza la BD
this.subirImagenActualizandoBD();
} else {
if(this.borrarArchivoImagen) {
this.eliminarArchivo(this.document.data.imagen);
this.document.data.imagen = null;
}
// Si no ha cambiado la imagen no se sube como archivo, sólo se actualiza la BD
this.actualizarBaseDatos();
}
}
async subirImagenActualizandoBD(){
// Mensaje de espera mientras se sube la imagen
const loading = await this.loadingController.create({
message: 'Please wait...'
});
// Mensaje de finalización de subida de la imagen
const toast = await this.toastController.create({
message: 'Image was updated successfully',
duration: 3000
});
// Carpeta del Storage donde se almacenará la imagen
let nombreCarpeta = "imagenes";
// Mostrar el mensaje de espera
loading.present();
// Asignar el nombre de la imagen en función de la hora actual para
// evitar duplicidades de nombres
let nombreImagen = `${new Date().getTime()}`;
// Llamar al método que sube la imagen al Storage
this.firestoreService.subirImagenBase64(nombreCarpeta, nombreImagen, this.imagenTempSrc)
.then(snapshot => {
snapshot.ref.getDownloadURL()
.then(downloadURL => {
// En la variable downloadURL se tiene la dirección de descarga de la imagen
console.log("downloadURL:" + downloadURL);
//this.document.data.imagenURL = downloadURL;
// Mostrar el mensaje de finalización de la subida
toast.present();
// Ocultar mensaje de espera
loading.dismiss();
// Una vez que se ha termninado la subida de la imagen
// se actualizan los datos en la BD
this.document.data.imagen = downloadURL;
this.actualizarBaseDatos();
})
})
}
public borrarImagen() {
// No mostrar ninguna imagen en la página
this.imagenTempSrc = null;
// Se informa que no se debe subir ninguna imagen cuando se actualice la BD
this.subirArchivoImagen = false;
this.borrarArchivoImagen = true;
}
async eliminarArchivo(fileURL) {
const toast = await this.toastController.create({
message: 'File was deleted successfully',
duration: 3000
});
this.firestoreService.borrarArchivoPorURL(fileURL)
.then(() => {
toast.present();
}, (err) => {
console.log(err);
});
}
private actualizarBaseDatos() {
console.log("Guardando en la BD: ");
console.log(this.document.data);
this.firestoreService.actualizar(this.reparacciones, this.document.id, this.document.data);
}
text: string='Precio'
link: string='https://ionicframework.com/'
ShareGeneric(parameter){
const url = this.link
const text = this.text + this.document.data.precio
this.socialSharing.share(this.document.data.nombre, 'REPARACIÓN', null, this.document.data.imagen)
}
ionViewDidEnter() {
this.isLogged = false;
this.afAuth.user.subscribe(user => {
if(user){
this.userEmail = user.email;
this.userUID = user.uid;
this.isLogged = true;
} else {
this.router.navigate(["/home"]);
}
})
}
}
Archivo Informacion.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CallNumber } from '@awesome-cordova-plugins/call-number/ngx';
//import {Map, tileLayer} from 'leaflet';
import * as L from 'leaflet';
@Component({
selector: 'app-informacion',
templateUrl: './informacion.page.html',
styleUrls: ['./informacion.page.scss'],
})
export class InformacionPage implements OnInit {
map: L.Map;
constructor(private router:Router,private callNumber:CallNumber) { }
llamada(){
this.callNumber.callNumber('3521234567', true)
.then(() => console.log('Llamada exitosa!'))
.catch(() => console.log('Error al intentar llamar'));
}
pasarPrimeraPantalla () {
this.router.navigate(['home'])
}
ionViewDidEnter(){
this.loadMap();
}
loadMap() {
var customIcon = new L.Icon({
iconUrl: 'https://img.icons8.com/color/48/000000/google-maps-new.png',
iconSize: [50, 50],
iconAnchor: [25, 50]
});
let latitud = 36.922349;
let longitud = -5.541855;
let zoom = 17;
this.map = L.map("mapId").setView([latitud, longitud], zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')
.addTo(this.map);
L.marker([ 36.922349,-5.541855],{icon: customIcon}).bindPopup("<p>Nos encontramos en la calle Torre Gailín Nº80</p>").addTo(this.map);
}
ngOnInit() {
}
}