commit 8c4294e67b3fa0fdf3fcf952b2de17e46dfd2179 Author: SIO Admin Date: Sun Jan 18 03:09:03 2026 +0000 feat: Codigo fuente SIO Mobile - App Android para Operadores Aplicacion movil Android para el sistema SIO (Sistema Integral de Operaciones) de Drenax. Permite a los operadores gestionar sus servicios diarios. Funcionalidades principales: - Login y autenticacion JWT - Checklist de vehiculos - Gestion de jornada laboral - Lista de servicios asignados - Captura de evidencias fotograficas - Firma digital del cliente - Encuestas de satisfaccion - Notificaciones push (Firebase) - Almacenamiento offline (ObjectBox) - Geolocalizacion y mapas Stack tecnologico: - Kotlin - Android SDK 33 - Retrofit + OkHttp - ObjectBox - Firebase (FCM, Crashlytics, Analytics) - Google Maps Co-Authored-By: Claude Opus 4.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7562f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +*.idea + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# OS X +.DS_STORE diff --git a/README.md b/README.md new file mode 100644 index 0000000..66f62b4 --- /dev/null +++ b/README.md @@ -0,0 +1,663 @@ +# SIO Mobile - Aplicacion Android para Operadores + +## Drenax - Sistema Integral de Operaciones + +--- + +## Informacion General + +| Campo | Valor | +|-------|-------| +| Nombre App | SIO Drenax | +| Package ID | com.iesoluciones.siodrenax | +| Version | 1.00 (versionCode: 3) | +| Plataforma | Android | +| SDK Minimo | 28 (Android 9.0 Pie) | +| SDK Target | 33 (Android 13) | +| Lenguaje | Kotlin | + +--- + +## Arquitectura del Sistema + +``` +┌─────────────────────────────────────────────────────────┐ +│ APP MOVIL │ +│ Android (Kotlin) │ +│ com.iesoluciones.siodrenax │ +└─────────────────────┬───────────────────────────────────┘ + │ HTTPS (REST API) + ▼ +┌─────────────────────────────────────────────────────────┐ +│ BACKEND │ +│ Laravel 5.5 (PHP 7.4) │ +│ https://sio-api.consultoria-as.com │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## Estructura del Proyecto + +``` +SIO-Mobile/ +├── app/ +│ ├── build.gradle # Configuracion de compilacion +│ └── src/ +│ └── main/ +│ ├── AndroidManifest.xml # Configuracion de la app +│ ├── java/com/iesoluciones/siodrenax/ +│ │ ├── activities/ # Pantallas de la app +│ │ │ ├── SplashActivity.kt +│ │ │ ├── LoginActivity.kt +│ │ │ ├── WorkdayActivity.kt +│ │ │ ├── OrdersActivity.kt +│ │ │ ├── OrderDetailActivity.kt +│ │ │ ├── OrderProgressActivity.kt +│ │ │ ├── CameraActivity.kt +│ │ │ ├── SignatureActivity.kt +│ │ │ ├── SurveyActivity.kt +│ │ │ └── ConfirmationActivity.kt +│ │ ├── adapters/ # Adaptadores RecyclerView +│ │ ├── entities/ # Entidades ObjectBox (DB local) +│ │ ├── interfaces/ # Interfaces +│ │ ├── models/ # Modelos de datos +│ │ ├── network/ # Configuracion API (Retrofit) +│ │ ├── services/ # Servicios (notificaciones) +│ │ │ └── NotificationService.kt +│ │ └── utils/ # Utilidades +│ └── res/ # Recursos +│ ├── layout/ # Layouts XML +│ ├── values/ # Strings, colors, styles +│ ├── drawable/ # Iconos e imagenes +│ └── mipmap/ # Iconos de app +├── gradle/ +├── mylibrary/ # Libreria auxiliar +├── build.gradle # Configuracion proyecto +├── settings.gradle +└── google-services.json # Configuracion Firebase +``` + +--- + +## Pantallas de la Aplicacion + +| Activity | Descripcion | +|----------|-------------| +| SplashActivity | Pantalla de inicio/carga | +| LoginActivity | Inicio de sesion | +| WorkdayActivity | Inicio/fin de jornada laboral | +| OrdersActivity | Lista de servicios asignados | +| OrderDetailActivity | Detalle de un servicio | +| OrderProgressActivity | Progreso del servicio | +| CameraActivity | Captura de fotografias | +| SignatureActivity | Captura de firma del cliente | +| SurveyActivity | Encuestas de satisfaccion | +| ConfirmationActivity | Confirmacion de servicio | +| NextServiceActivity | Servicios del dia siguiente | +| PdfViewerActivity | Visualizador de PDF | +| OperatorsActivity | Lista de operadores (supervisor) | +| WorkdayManagerActivity | Gestion de jornada (supervisor) | +| OrdersManagerActivity | Gestion de ordenes (supervisor) | +| HerramientaSurveyActivity | Encuesta de herramientas | +| MaterialSurveyActivity | Encuesta de materiales | +| RevisionSurveyActivity | Encuesta de revision | + +--- + +## Flujos de la Aplicacion + +### Flujo Operador + +``` +1. Splash Screen + ↓ +2. Login (credenciales) + ↓ +3. Checklist Vehiculo + ↓ +4. Iniciar Jornada + ├── Seleccionar vehiculo + ├── Registrar kilometraje inicial + └── Capturar ubicacion GPS + ↓ +5. Ver Lista de Servicios del Dia + ↓ +6. Seleccionar Servicio + ↓ +7. Ver Detalle del Servicio + ├── Informacion del cliente + ├── Direccion (con mapa) + ├── Tipo de servicio + └── Observaciones + ↓ +8. Iniciar Servicio + └── Registra ubicacion GPS + ↓ +9. Realizar Trabajo + ↓ +10. Capturar Evidencias + ├── Foto antes + ├── Foto durante + └── Foto despues + ↓ +11. Encuestas + ├── Herramientas utilizadas + ├── Materiales empleados + └── Revision general + ↓ +12. Capturar Firma del Cliente + ↓ +13. Finalizar Servicio + ├── Registrar litraje + └── Comentarios + ↓ +14. Siguiente Servicio o Fin de Jornada + ├── Mas servicios → Paso 6 + └── Fin jornada → Registrar kilometraje final +``` + +### Flujo Supervisor + +``` +1. Login + ↓ +2. Iniciar Jornada Supervisor + ↓ +3. Ver Lista de Operadores + ↓ +4. Seleccionar Operador + ↓ +5. Ver Servicios del Operador + ├── Estatus de cada servicio + ├── Ubicacion actual + └── Progreso del dia + ↓ +6. Monitorear en Tiempo Real + ↓ +7. Fin de Jornada +``` + +--- + +## Configuracion de API + +### URLs de Conexion + +```kotlin +// En app/build.gradle - buildTypes + +// Produccion +buildConfigField("String", "BASE_URL", '"https://sio-api.consultoria-as.com/api/"') +buildConfigField("String", "STORAGE_URL", '"https://sio-api.consultoria-as.com/storage/"') + +// Desarrollo (opcional) +buildConfigField("String", "BASE_URL", '"http://192.168.1.100:8000/api/"') +buildConfigField("String", "STORAGE_URL", '"http://192.168.1.100:8000/storage/"') +``` + +### Archivo de Configuracion + +Ubicacion: `app/build.gradle` + +```gradle +android { + defaultConfig { + applicationId "com.iesoluciones.siodrenax" + minSdkVersion 28 + targetSdkVersion 33 + versionCode 3 + versionName "1.00" + } + + buildTypes { + debug { + buildConfigField("String", "BASE_URL", '"https://sio-api.consultoria-as.com/api/"') + buildConfigField("String", "STORAGE_URL", '"https://sio-api.consultoria-as.com/storage/"') + } + release { + minifyEnabled false + buildConfigField("String", "BASE_URL", '"https://sio-api.consultoria-as.com/api/"') + buildConfigField("String", "STORAGE_URL", '"https://sio-api.consultoria-as.com/storage/"') + } + } +} +``` + +--- + +## API Endpoints Utilizados + +### Autenticacion + +| Metodo | Endpoint | Descripcion | +|--------|----------|-------------| +| POST | /login | Iniciar sesion | + +### Operador + +| Metodo | Endpoint | Descripcion | +|--------|----------|-------------| +| GET | /operador/checklist | Obtener checklist del vehiculo | +| GET | /operador/checklist/vehiculos | Obtener vehiculos disponibles | +| POST | /operador/checklist | Enviar checklist completado | +| POST | /operador/iniciarjornada | Iniciar jornada laboral | +| POST | /operador/finalizarjornada/{id} | Finalizar jornada | +| GET | /operador/solicitud_servicios | Obtener servicios asignados | +| GET | /operador/solicitud_servicios/{id} | Detalle de servicio | +| POST | /operador/solicitud_servicios/iniciar | Iniciar servicio | +| POST | /operador/solicitud_servicios/finalizar-new | Finalizar servicio | +| POST | /operador/solicitud_servicios/verificar/servicios | Verificar servicios | +| GET | /operador/servicios/diasiguiente | Servicios del dia siguiente | +| POST | /operador/vehiculos_incidencias | Reportar incidencia vehiculo | +| POST | /operador/vehiculos_incidencias/ultima_incidencia | Ultima incidencia | +| PUT | /operador/vehiculos_incidencias/{id}/resolver | Resolver incidencia | + +### Supervisor de Operaciones + +| Metodo | Endpoint | Descripcion | +|--------|----------|-------------| +| GET | /supervisoroperaciones/asesores | Listar operadores | +| GET | /supervisoroperaciones/asesores/{id}/servicios | Servicios de operador | +| POST | /supervisoroperaciones/iniciarjornada | Iniciar jornada supervisor | +| POST | /supervisoroperaciones/finalizarjornada/{id} | Finalizar jornada | + +--- + +## Dependencias Principales + +### Networking (Retrofit + OkHttp) + +```gradle +implementation 'com.squareup.retrofit2:retrofit:2.5.0' +implementation 'com.squareup.retrofit2:converter-gson:2.4.0' +implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' +implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' +``` + +### Base de Datos Local (ObjectBox) + +```gradle +implementation "io.objectbox:objectbox-kotlin:$objectboxVersion" +debugImplementation "io.objectbox:objectbox-android-objectbrowser:$objectboxVersion" +``` + +Permite almacenamiento offline de: +- Datos de sesion +- Servicios descargados +- Fotos pendientes de subir +- Respuestas de encuestas + +### Firebase + +```gradle +implementation 'com.google.firebase:firebase-messaging:22.0.0' +implementation 'com.google.firebase:firebase-crashlytics:18.2.0' +implementation 'com.google.firebase:firebase-core:19.0.0' +``` + +Servicios utilizados: +- **FCM:** Notificaciones push +- **Crashlytics:** Reportes de errores +- **Analytics:** Estadisticas de uso + +### UI/UX + +```gradle +implementation 'com.google.android.material:material:1.5.0' +implementation 'androidx.constraintlayout:constraintlayout:2.1.3' +implementation 'com.squareup.picasso:picasso:2.71828' +implementation 'com.github.bumptech.glide:glide:4.11.0' +``` + +### Mapas + +```gradle +implementation 'com.google.android.gms:play-services-maps:18.0.2' +``` + +### Camara y Firma + +```gradle +implementation 'io.fotoapparat:fotoapparat:2.7.0' // Camara +implementation 'com.github.gcacace:signature-pad:1.3.1' // Firma digital +``` + +### PDF + +```gradle +implementation 'io.github.nvest-solutions:html-to-pdf-convertor:1.0.0' +implementation 'com.github.naya-aastra:SkewPdfView:1.1' +``` + +--- + +## Permisos Requeridos + +| Permiso | Uso | +|---------|-----| +| INTERNET | Conexion al API | +| CALL_PHONE | Llamar al cliente | +| FOREGROUND_SERVICE | Servicios en segundo plano | +| VIBRATE | Notificaciones | +| WAKE_LOCK | Mantener pantalla activa | +| READ_PHONE_STATE | Identificar dispositivo | +| READ_EXTERNAL_STORAGE | Leer fotos | +| WRITE_EXTERNAL_STORAGE | Guardar fotos/archivos | +| ACCESS_FINE_LOCATION | GPS preciso | +| ACCESS_COARSE_LOCATION | GPS aproximado | +| ACCESS_NOTIFICATION_POLICY | Gestionar notificaciones | +| CAMERA | Tomar fotografias | + +--- + +## Claves y Configuracion + +### Google Maps API Key + +```xml + + +``` + +### Firebase + +Archivo de configuracion: `app/google-services.json` + +**Nota:** Este archivo contiene las credenciales de Firebase y debe mantenerse privado. + +--- + +## Compilacion + +### Requisitos + +- Android Studio Arctic Fox o superior +- JDK 8 o superior +- Gradle 7.x +- Android SDK 33 + +### Compilar Debug APK + +```bash +cd SIO-Mobile +./gradlew assembleDebug +``` + +APK generado en: `app/build/outputs/apk/debug/app-debug.apk` + +### Compilar Release APK + +```bash +./gradlew assembleRelease +``` + +APK generado en: `app/build/outputs/apk/release/app-release.apk` + +**Nota:** Para release necesitas configurar signing en `build.gradle`: + +```gradle +android { + signingConfigs { + release { + storeFile file("keystore.jks") + storePassword "password" + keyAlias "alias" + keyPassword "password" + } + } + buildTypes { + release { + signingConfig signingConfigs.release + } + } +} +``` + +### Instalar en Dispositivo + +```bash +# Via ADB +adb install app/build/outputs/apk/debug/app-debug.apk + +# O directamente desde Android Studio +# Run > Run 'app' +``` + +--- + +## Notificaciones Push (Firebase) + +### Servicio + +```kotlin +// com.iesoluciones.siodrenax.services.NotificationService +``` + +### Tipos de Notificaciones + +| Tipo | Descripcion | +|------|-------------| +| Nuevo servicio | Servicio asignado al operador | +| Cambio estatus | Actualizacion de estatus de servicio | +| Recordatorio | Recordatorio de servicio proximo | +| Mensaje | Mensaje del sistema/supervisor | + +### Configuracion en Backend + +El token FCM se registra en el login y se actualiza automaticamente. + +--- + +## Almacenamiento Local (ObjectBox) + +### Entidades Almacenadas + +```kotlin +// Sesion del usuario +@Entity +data class UserSession( + @Id var id: Long = 0, + var userId: Int, + var token: String, + var nombre: String, + var email: String +) + +// Servicio en cache +@Entity +data class CachedService( + @Id var id: Long = 0, + var serviceId: Int, + var jsonData: String, + var syncStatus: Int +) + +// Evidencia pendiente +@Entity +data class PendingEvidence( + @Id var id: Long = 0, + var serviceId: Int, + var photoPath: String, + var type: String, + var uploaded: Boolean +) +``` + +### Sincronizacion + +La app sincroniza automaticamente cuando detecta conexion a internet: +- Sube evidencias pendientes +- Descarga servicios actualizados +- Envia encuestas completadas + +--- + +## Troubleshooting + +### App no conecta al servidor + +**Causa:** URL del API incorrecta o sin HTTPS + +**Solucion:** +1. Verificar `BASE_URL` en `app/build.gradle` +2. Asegurar que sea HTTPS: `https://sio-api.consultoria-as.com/api/` +3. Recompilar la app + +### Error de certificado SSL + +**Causa:** App configurada para HTTP, servidor usa HTTPS + +**Solucion:** +1. Actualizar URLs a HTTPS +2. Verificar en `AndroidManifest.xml`: +```xml +android:usesCleartextTraffic="false" +``` + +### Google Maps no carga + +**Causa:** API Key invalida o sin permisos + +**Solucion:** +1. Verificar API Key en Google Cloud Console +2. Habilitar Maps SDK for Android +3. Agregar restriccion de paquete: `com.iesoluciones.siodrenax` + +### Notificaciones no llegan + +**Causa:** Token Firebase no registrado + +**Solucion:** +1. Verificar `google-services.json` actualizado +2. Revisar permisos en Firebase Console +3. Verificar que el token se envie al backend en login + +### Fotos no se suben + +**Causa:** Conexion intermitente o timeout + +**Solucion:** +1. Las fotos se guardan localmente (ObjectBox) +2. Se suben automaticamente cuando hay conexion +3. Verificar en el log si hay errores de red + +### App crashea al iniciar + +**Causa:** Posible corrupcion de datos locales + +**Solucion:** +1. Limpiar datos de la app (Settings > Apps > SIO Drenax > Clear Data) +2. Si persiste, revisar Crashlytics para el error especifico + +--- + +## Actualizacion de la App + +### Cambiar Version + +En `app/build.gradle`: + +```gradle +defaultConfig { + versionCode 4 // Incrementar para cada release + versionName "1.01" // Version visible al usuario +} +``` + +### Generar APK Firmado (Android Studio) + +1. Build > Generate Signed Bundle/APK +2. Seleccionar APK +3. Usar keystore existente o crear nuevo +4. Seleccionar release +5. Generar + +### Distribucion + +- **Play Store:** Subir AAB (Android App Bundle) +- **Distribucion interna:** Compartir APK directamente +- **Firebase App Distribution:** Para pruebas beta + +--- + +## Estructura de Respuestas API + +### Login Exitoso + +```json +{ + "success": true, + "data": { + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "user": { + "id": 1, + "nombre": "Operador 1", + "email": "operador@mail.com", + "tipo_empleado_id": 2 + } + } +} +``` + +### Lista de Servicios + +```json +{ + "success": true, + "data": [ + { + "id": 123, + "cliente": "Juan Perez", + "direccion": "Calle 123, Colonia Centro", + "servicio": "Desazolve", + "hora": "09:00", + "estatus_id": 4, + "estatus_nombre": "Pendiente" + } + ] +} +``` + +### Error + +```json +{ + "success": false, + "error": "Token expirado", + "code": 401 +} +``` + +--- + +## Tecnologias Utilizadas + +- **Kotlin** - Lenguaje principal +- **Android SDK 33** - Target API +- **Retrofit** - Cliente HTTP +- **ObjectBox** - Base de datos local +- **Firebase** - Notificaciones y analytics +- **Google Maps** - Mapas y geolocalizacion +- **Fotoapparat** - Camara +- **Glide/Picasso** - Carga de imagenes +- **Material Design** - Componentes UI + +--- + +## Contacto + +| Campo | Valor | +|-------|-------| +| Sistema | SIO (Sistema Integral de Operaciones) | +| Empresa | Drenax | +| Package | com.iesoluciones.siodrenax | +| Desarrollo | IE Soluciones | +| Dominio | consultoria-as.com | + +--- + +**Version:** 1.0 +**Ultima actualizacion:** 2026-01-17 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..e3c940a --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,109 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-kapt' +} + +apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.google.firebase.crashlytics' + +android { + compileSdkVersion 33 + defaultConfig { + applicationId "com.iesoluciones.siodrenax" + minSdkVersion 28 + targetSdkVersion 33 + versionCode 3 + //versionName "0.48" + versionName "1.00" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true // To accept variables colors in vectors + } + + buildTypes { + debug { + // -------- API -------- + buildConfigField("String", "BASE_URL", '"https://sio-api.consultoria-as.com/api/"') //PROD - Cloudflare Tunnel + buildConfigField("String", "STORAGE_URL", '"https://sio-api.consultoria-as.com/storage/"') //PROD STORAGE + //buildConfigField("String", "BASE_URL", '"http://107.170.231.250/v1/api/"') //PROD OLD + //buildConfigField("String", "STORAGE_URL", '"http://107.170.231.250/storage/"') //PROD STORAGE OLD + } + + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + // -------- API -------- + buildConfigField("String", "BASE_URL", '"https://sio-api.consultoria-as.com/api/"') //PROD - Cloudflare Tunnel + buildConfigField("String", "STORAGE_URL", '"https://sio-api.consultoria-as.com/storage/"') //PROD STORAGE + //buildConfigField("String", "BASE_URL", '"http://107.170.231.250/v1/api/"') //PROD OLD + //buildConfigField("String", "STORAGE_URL", '"http://107.170.231.250/storage/"') //PROD STORAGE OLD + } + } + + buildFeatures { + viewBinding true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.work:work-runtime:2.8.0' + implementation 'com.google.android.material:material:1.5.0' + + implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // Android Jetpack + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' //Android Jetpack + implementation "android.arch.work:work-runtime:1.0.1" //Android Jetpack //Android Jetpack + + implementation 'com.squareup.retrofit2:retrofit:2.5.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' //RxAndroid + implementation 'io.reactivex.rxjava2:rxjava:2.2.2' //RxJava + implementation 'com.squareup.retrofit2:converter-gson:2.4.0' //GSON Converter for Retrofit + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' //Rx Requests for Retrofit + implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' //Logging interceptor for every Request + implementation 'com.github.bumptech.glide:glide:4.11.0' + kapt 'com.github.bumptech.glide:compiler:4.11.0' + implementation 'com.google.android.gms:play-services-maps:18.0.2'// Google Maps Android API + + // ----- ObjectBox Data Browser ----- + debugImplementation "io.objectbox:objectbox-android-objectbrowser:$objectboxVersion" + releaseImplementation "io.objectbox:objectbox-android:$objectboxVersion" + implementation "io.objectbox:objectbox-kotlin:$objectboxVersion" + + // ----- Testing ----- + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + // ----- Firebase ----- + implementation 'com.google.firebase:firebase-messaging:22.0.0' + implementation 'com.google.firebase:firebase-crashlytics:18.2.0' + implementation 'com.google.firebase:firebase-core:19.0.0' + implementation platform('com.google.firebase:firebase-bom:28.3.1') + implementation 'com.google.firebase:firebase-crashlytics-ktx' + implementation 'com.google.firebase:firebase-analytics-ktx' + + // ----- Camera & Sign ----- + implementation 'io.fotoapparat:fotoapparat:2.7.0' + implementation 'com.github.gcacace:signature-pad:1.3.1' + + // ----- HTML TO PDF ----- + implementation 'io.github.nvest-solutions:html-to-pdf-convertor:1.0.0' + implementation 'com.github.naya-aastra:SkewPdfView:1.1' + implementation project(':mylibrary') +} + +apply plugin: 'io.objectbox' // Apply last. \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..27b198a --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "108152813393", + "firebase_url": "https://drenaxx-f561e.firebaseio.com", + "project_id": "drenaxx-f561e", + "storage_bucket": "drenaxx-f561e.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:108152813393:android:6a2ce608fab88e98", + "android_client_info": { + "package_name": "com.iesoluciones.siodrenax" + } + }, + "oauth_client": [ + { + "client_id": "108152813393-sui8jok0le3f873uoceguvrss61r8e8s.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.iesoluciones.siodrenax", + "certificate_hash": "43d5f941fa659b3f3e874fce11ff528d5ce26f1e" + } + }, + { + "client_id": "108152813393-g09ea87ufqm21om9qt2v45fgl079d704.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyB05HH7lJlT0AIlILWlit467ArJ11W5vTc" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "108152813393-g09ea87ufqm21om9qt2v45fgl079d704.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/objectbox-models/default.json b/app/objectbox-models/default.json new file mode 100644 index 0000000..d8b4d71 --- /dev/null +++ b/app/objectbox-models/default.json @@ -0,0 +1,1153 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:5665101293431379024", + "lastPropertyId": "15:208191760973984819", + "name": "User", + "properties": [ + { + "id": "1:5321525569462566807", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:5712919134554378309", + "name": "email", + "type": 9 + }, + { + "id": "11:2978276056752430768", + "name": "nombre", + "type": 9 + }, + { + "id": "12:5508122438903068159", + "name": "apellido_paterno", + "type": 9 + }, + { + "id": "13:2241675499279008452", + "name": "apellido_materno", + "type": 9 + }, + { + "id": "14:8281964782181768919", + "name": "telefono", + "type": 9 + }, + { + "id": "15:208191760973984819", + "name": "tipo_empleado_id", + "type": 6 + } + ], + "relations": [] + }, + { + "id": "2:4372468123517637909", + "lastPropertyId": "16:1177766786685605044", + "name": "CheckListQuestion", + "properties": [ + { + "id": "1:3400228994932651467", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:8650406753973225582", + "name": "nombre", + "type": 9 + }, + { + "id": "6:1643483388542391711", + "name": "tipo", + "type": 9 + }, + { + "id": "10:8688927534108840815", + "name": "fecha", + "type": 9 + }, + { + "id": "11:2400276669962255845", + "name": "tipo_radio_btn", + "type": 5 + }, + { + "id": "12:6562151703877694550", + "name": "tipo_text", + "type": 5 + }, + { + "id": "13:602620057792595382", + "name": "tipo_checkbox", + "type": 5 + }, + { + "id": "14:2026798835348264681", + "name": "respuesta_radio_btn", + "type": 9 + }, + { + "id": "15:7423604211200632430", + "name": "respuesta_text", + "type": 9 + }, + { + "id": "16:1177766786685605044", + "name": "respuesta_checkbox", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "3:4377792362946807137", + "lastPropertyId": "2:670135943743083929", + "name": "Vehicle", + "properties": [ + { + "id": "1:4705575517894212104", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:670135943743083929", + "name": "nombre", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "4:9026394323396024465", + "lastPropertyId": "65:2818775822293115291", + "name": "Order", + "properties": [ + { + "id": "1:4790167818510396202", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:5822431497542564601", + "name": "idService", + "type": 6 + }, + { + "id": "3:368051226835786410", + "name": "idRequest", + "type": 6 + }, + { + "id": "4:5299115277318286719", + "name": "serviceName", + "type": 9 + }, + { + "id": "5:8509828799642636144", + "name": "idServiceStatus", + "type": 6 + }, + { + "id": "6:758228903305954550", + "name": "serviceStatusName", + "type": 9 + }, + { + "id": "7:3565593439842136067", + "name": "serviceStatusColor1", + "type": 9 + }, + { + "id": "8:1414901339937410644", + "name": "serviceStatusColor2", + "type": 9 + }, + { + "id": "9:766211398812222140", + "name": "idPayMethod", + "type": 6 + }, + { + "id": "10:5147361690223074723", + "name": "payMethodName", + "type": 9 + }, + { + "id": "11:4500258100657127431", + "name": "idServiceType", + "type": 6 + }, + { + "id": "12:8196932021437273167", + "name": "comments", + "type": 9 + }, + { + "id": "13:5865529873029354021", + "name": "cost", + "type": 9 + }, + { + "id": "14:4506031685435688370", + "name": "serviceTypeName", + "type": 9 + }, + { + "id": "15:8767456587919863166", + "name": "dateScheduled", + "type": 9 + }, + { + "id": "16:390465234933298875", + "name": "dateServiceRequest", + "type": 9 + }, + { + "id": "17:2232773430973000819", + "name": "idUserSchedule", + "type": 6 + }, + { + "id": "18:16974732538945366", + "name": "userScheduleName", + "type": 9 + }, + { + "id": "19:8555992252505178313", + "name": "userScheduleLastName", + "type": 9 + }, + { + "id": "20:6193392468828583706", + "name": "getUserScheduleMothersLastName", + "type": 9 + }, + { + "id": "21:4660796289579119889", + "name": "duracion", + "type": 9 + }, + { + "id": "22:3784527562475848084", + "name": "clientDefined", + "type": 6 + }, + { + "id": "23:622745567829234352", + "name": "idClient", + "type": 6 + }, + { + "id": "24:3488955318550079798", + "name": "clientDenomination", + "type": 9 + }, + { + "id": "25:1043990105761854142", + "name": "clientContactName", + "type": 9 + }, + { + "id": "26:2306458271923532534", + "name": "getClientContactCellphone", + "type": 9 + }, + { + "id": "27:8427015918175385673", + "name": "idClientAddress", + "type": 6 + }, + { + "id": "28:5613227685156295965", + "name": "streetAddress", + "type": 9 + }, + { + "id": "29:626834541446943773", + "name": "extNumberAddress", + "type": 9 + }, + { + "id": "30:3097111174238127635", + "name": "intNumberAddress", + "type": 9 + }, + { + "id": "31:2579197447570912033", + "name": "neighborhoodNameAddress", + "type": 9 + }, + { + "id": "32:8725626942246743515", + "name": "clientPostalCodeAddress", + "type": 9 + }, + { + "id": "33:7167249096611278992", + "name": "clientPhone", + "type": 9 + }, + { + "id": "34:1717487907822141185", + "name": "clientLat", + "type": 9 + }, + { + "id": "35:3923208556696213877", + "name": "clientLng", + "type": 9 + }, + { + "id": "36:2587769480300510869", + "name": "idOperator", + "type": 6 + }, + { + "id": "37:7098221334791768219", + "name": "operatorName", + "type": 9 + }, + { + "id": "38:2569311124845582577", + "name": "operatorLastName", + "type": 9 + }, + { + "id": "39:2199904261882308305", + "name": "operatorMothersName", + "type": 9 + }, + { + "id": "40:7770932078561947689", + "name": "idVehicle", + "type": 6 + }, + { + "id": "41:98427989004918261", + "name": "vehicleCodeName", + "type": 9 + }, + { + "id": "42:2451788547455986315", + "name": "vehicleOfficeName", + "type": 9 + }, + { + "id": "43:7831510545763858022", + "name": "idVehicleOffice", + "type": 6 + }, + { + "id": "44:8113716121116249544", + "name": "operatorOfficeName", + "type": 9 + }, + { + "id": "45:5857477213726475000", + "name": "idOperatorOffice", + "type": 6 + }, + { + "id": "46:1843499857429967030", + "name": "idAssistant1", + "type": 6 + }, + { + "id": "47:4275027260571981678", + "name": "idAssistant2", + "type": 6 + }, + { + "id": "48:7964662182008538023", + "name": "assistant1Name", + "type": 9 + }, + { + "id": "49:8566831797326428251", + "name": "assistant2Name", + "type": 9 + }, + { + "id": "50:9132735670991254606", + "name": "assistant1OfficeName", + "type": 9 + }, + { + "id": "51:1462071515349543367", + "name": "assistant2OfficeName", + "type": 9 + }, + { + "id": "52:3238931045124412371", + "name": "SurveyRequired", + "type": 5 + }, + { + "id": "53:2207836726115018568", + "name": "isSent", + "type": 1 + }, + { + "id": "57:2645779966994040528", + "name": "assistant1LastName", + "type": 9 + }, + { + "id": "58:3608510605234859928", + "name": "assistant1MothersName", + "type": 9 + }, + { + "id": "59:992561270306835037", + "name": "clientCity", + "type": 9 + }, + { + "id": "60:3211907636147776442", + "name": "negativeCost", + "type": 9 + }, + { + "id": "61:5525637547111531029", + "name": "pdfPath", + "type": 9 + }, + { + "id": "63:7095836277252619975", + "name": "sketchName", + "type": 9 + }, + { + "id": "64:7631710765117535428", + "name": "sketchPath", + "type": 9 + }, + { + "id": "65:2818775822293115291", + "name": "branchName", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "5:2910966717665207605", + "lastPropertyId": "2:359659400093111854", + "name": "NegativeServiceReason", + "properties": [ + { + "id": "1:8047838382872767093", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:359659400093111854", + "name": "descripcion", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "6:6355541814294916142", + "lastPropertyId": "5:1116445808186977890", + "name": "BusinessQuestion", + "properties": [ + { + "id": "1:9037400062941888607", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:231310261271246192", + "name": "nombre", + "type": 9 + }, + { + "id": "3:2611867171191406507", + "name": "orden", + "type": 5 + }, + { + "id": "4:4644663952015206374", + "name": "mostrar_numero", + "type": 5 + }, + { + "id": "5:1116445808186977890", + "name": "obligatorio", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "7:2859876280723130335", + "lastPropertyId": "5:8986637898173850281", + "name": "BusinessAnswer", + "properties": [ + { + "id": "1:1827935606743061935", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:8469863193806391937", + "name": "pregunta_id", + "type": 6 + }, + { + "id": "3:8600426673309812655", + "name": "nombre", + "type": 9 + }, + { + "id": "4:4263203625425115275", + "name": "orden", + "type": 5 + }, + { + "id": "5:8986637898173850281", + "name": "tipo_campo", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "8:6173653981125352089", + "lastPropertyId": "5:4524742060668120515", + "name": "DomesticAnswer", + "properties": [ + { + "id": "1:1246178104144041976", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:9022364118858654246", + "name": "pregunta_id", + "type": 6 + }, + { + "id": "3:1328613281292912347", + "name": "nombre", + "type": 9 + }, + { + "id": "4:6284466592499946392", + "name": "orden", + "type": 5 + }, + { + "id": "5:4524742060668120515", + "name": "tipo_campo", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "9:5835999145885427316", + "lastPropertyId": "5:7827790482540953055", + "name": "DomesticQuestion", + "properties": [ + { + "id": "1:7983234636383478791", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:3482813846550658400", + "name": "nombre", + "type": 9 + }, + { + "id": "3:9092368960227262920", + "name": "orden", + "type": 5 + }, + { + "id": "4:2553545940366448952", + "name": "mostrar_numero", + "type": 5 + }, + { + "id": "5:7827790482540953055", + "name": "obligatorio", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "10:5265773309037872977", + "lastPropertyId": "11:4204834214255679706", + "name": "Evidence", + "properties": [ + { + "id": "1:5097679613150378396", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:7852987476761877434", + "name": "evidenceNo", + "type": 6 + }, + { + "id": "3:6189591302802867443", + "name": "idOrder", + "type": 6 + }, + { + "id": "4:861441360886912220", + "name": "idRequest", + "type": 6 + }, + { + "id": "5:1047576737599164892", + "name": "type", + "type": 5 + }, + { + "id": "6:8546869156481956087", + "name": "path", + "type": 9 + }, + { + "id": "7:2483671667245694693", + "name": "lat", + "type": 9 + }, + { + "id": "8:6829820330214152801", + "name": "lng", + "type": 9 + }, + { + "id": "9:5161753908652537972", + "name": "viewRef", + "type": 5 + }, + { + "id": "10:2272502976273257358", + "name": "isSent", + "type": 1 + }, + { + "id": "11:4204834214255679706", + "name": "name", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "11:6475205932376807337", + "lastPropertyId": "53:1912263222050268605", + "name": "NextDayOrder", + "properties": [ + { + "id": "1:3264868661678103871", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:4456053841841817525", + "name": "idService", + "type": 6 + }, + { + "id": "3:7997711242381267225", + "name": "idRequest", + "type": 6 + }, + { + "id": "4:8271086619232851703", + "name": "serviceName", + "type": 9 + }, + { + "id": "5:7245352167591810796", + "name": "idServiceStatus", + "type": 6 + }, + { + "id": "6:5241727878047629545", + "name": "serviceStatusName", + "type": 9 + }, + { + "id": "7:4800279603731351311", + "name": "serviceStatusColor1", + "type": 9 + }, + { + "id": "8:1503098083362916520", + "name": "serviceStatusColor2", + "type": 9 + }, + { + "id": "9:4162194526312159096", + "name": "idPayMethod", + "type": 6 + }, + { + "id": "10:2343353027460528639", + "name": "payMethodName", + "type": 9 + }, + { + "id": "11:6561915191130038168", + "name": "idServiceType", + "type": 6 + }, + { + "id": "12:6839695974332536393", + "name": "comments", + "type": 9 + }, + { + "id": "13:1681165445808619896", + "name": "cost", + "type": 9 + }, + { + "id": "14:9201579815421133529", + "name": "serviceTypeName", + "type": 9 + }, + { + "id": "15:3532685957779838245", + "name": "dateScheduled", + "type": 9 + }, + { + "id": "16:1013511014762768893", + "name": "dateServiceRequest", + "type": 9 + }, + { + "id": "17:5141030834751053579", + "name": "idUserSchedule", + "type": 6 + }, + { + "id": "18:8316834618735938730", + "name": "userScheduleName", + "type": 9 + }, + { + "id": "19:3183152972501875602", + "name": "userScheduleLastName", + "type": 9 + }, + { + "id": "20:5109048459257258350", + "name": "getUserScheduleMothersLastName", + "type": 9 + }, + { + "id": "21:1871730937425078021", + "name": "duracion", + "type": 9 + }, + { + "id": "22:1907694265195596804", + "name": "clientDefined", + "type": 6 + }, + { + "id": "23:6625662055987934614", + "name": "idClient", + "type": 6 + }, + { + "id": "24:8843797071646769572", + "name": "clientDenomination", + "type": 9 + }, + { + "id": "25:9072820172183837569", + "name": "clientContactName", + "type": 9 + }, + { + "id": "26:6655742830533723645", + "name": "getClientContactCellphone", + "type": 9 + }, + { + "id": "27:4178398874819395513", + "name": "idClientAddress", + "type": 6 + }, + { + "id": "28:684820188619097939", + "name": "streetAddress", + "type": 9 + }, + { + "id": "29:8119650734803796332", + "name": "extNumberAddress", + "type": 9 + }, + { + "id": "30:8725509132974294337", + "name": "intNumberAddress", + "type": 9 + }, + { + "id": "31:559116993585388728", + "name": "neighborhoodNameAddress", + "type": 9 + }, + { + "id": "32:722618039033631165", + "name": "clientPostalCodeAddress", + "type": 9 + }, + { + "id": "33:5122355857579820755", + "name": "clientPhone", + "type": 9 + }, + { + "id": "34:5061809862649615222", + "name": "clientLat", + "type": 9 + }, + { + "id": "35:4421805919905742210", + "name": "clientLng", + "type": 9 + }, + { + "id": "36:16208392747324573", + "name": "idOperator", + "type": 6 + }, + { + "id": "37:1010399692004420493", + "name": "operatorName", + "type": 9 + }, + { + "id": "38:3377567735585312268", + "name": "operatorLastName", + "type": 9 + }, + { + "id": "39:3462303112734700369", + "name": "operatorMothersName", + "type": 9 + }, + { + "id": "40:8652651578878147290", + "name": "idVehicle", + "type": 6 + }, + { + "id": "41:7910381791883008711", + "name": "vehicleCodeName", + "type": 9 + }, + { + "id": "42:9132815036783490247", + "name": "vehicleOfficeName", + "type": 9 + }, + { + "id": "43:7372464010650191612", + "name": "idVehicleOffice", + "type": 6 + }, + { + "id": "44:1418155582741812092", + "name": "operatorOfficeName", + "type": 9 + }, + { + "id": "45:1573622015946405668", + "name": "idOperatorOffice", + "type": 6 + }, + { + "id": "46:8767471268383720558", + "name": "idAssistant1", + "type": 6 + }, + { + "id": "47:3801283899175927660", + "name": "idAssistant2", + "type": 6 + }, + { + "id": "48:4218765227230057063", + "name": "assistant1Name", + "type": 9 + }, + { + "id": "49:4638061793666351178", + "name": "assistant2Name", + "type": 9 + }, + { + "id": "50:7070724765117443211", + "name": "assistant1OfficeName", + "type": 9 + }, + { + "id": "51:5901287936339728517", + "name": "assistant2OfficeName", + "type": 9 + }, + { + "id": "52:3442054022641070646", + "name": "SurveyRequired", + "type": 5 + }, + { + "id": "53:1912263222050268605", + "name": "isSent", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "12:6957973966007774656", + "lastPropertyId": "21:4053300102365953297", + "name": "OrderProgress", + "properties": [ + { + "id": "1:4909409867696772588", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:2376195153552155877", + "name": "idRequest", + "type": 6 + }, + { + "id": "3:8737352292837009750", + "name": "startDate", + "type": 9 + }, + { + "id": "4:8420872139229865602", + "name": "endDate", + "type": 9 + }, + { + "id": "5:4280475918090828107", + "name": "comments", + "type": 9 + }, + { + "id": "6:1833202438305001872", + "name": "elapsedTime", + "type": 9 + }, + { + "id": "7:4615340923415236343", + "name": "startLat", + "type": 9 + }, + { + "id": "8:1860619079461141044", + "name": "startLng", + "type": 9 + }, + { + "id": "9:7560964551558538330", + "name": "endLat", + "type": 9 + }, + { + "id": "10:3899822933000985859", + "name": "endLng", + "type": 9 + }, + { + "id": "11:7652484697176584591", + "name": "signaturePath", + "type": 9 + }, + { + "id": "12:6492043671717914407", + "name": "warranty", + "type": 1 + }, + { + "id": "13:2408814832806683450", + "name": "isSent", + "type": 1 + }, + { + "id": "14:7034569438693192646", + "name": "shouldBeSent", + "type": 1 + }, + { + "id": "15:2804509603161477239", + "name": "signatureSent", + "type": 1 + }, + { + "id": "16:4180953079476690512", + "name": "isOnSurvey", + "type": 1 + }, + { + "id": "17:3644568522287365574", + "name": "shouldSendSurvey", + "type": 1 + }, + { + "id": "18:6301736258721676414", + "name": "surveySent", + "type": 1 + }, + { + "id": "19:7669451713533174054", + "name": "liters", + "type": 5 + }, + { + "id": "20:3018195537892130994", + "name": "negativeService", + "type": 5 + }, + { + "id": "21:4053300102365953297", + "name": "signatureName", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "13:1724014081733562326", + "lastPropertyId": "5:7510073138677565100", + "name": "SavedAnswer", + "properties": [ + { + "id": "1:1211012512255883947", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:4555912609217419213", + "name": "questionId", + "type": 6 + }, + { + "id": "3:4690130029848358340", + "name": "orderId", + "type": 6 + }, + { + "id": "4:1609083068078937709", + "name": "isOpen", + "type": 1 + }, + { + "id": "5:7510073138677565100", + "name": "answer", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "14:6869636155040222828", + "lastPropertyId": "6:4385486216402212475", + "name": "Operator", + "properties": [ + { + "id": "1:8291666170904199724", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:8545676381506904920", + "name": "nombre", + "type": 9 + }, + { + "id": "3:3719435367169286614", + "name": "apellido_paterno", + "type": 9 + }, + { + "id": "4:878061371212085518", + "name": "apellido_materno", + "type": 9 + }, + { + "id": "5:1604294184145832145", + "name": "servicios_total", + "type": 6 + }, + { + "id": "6:4385486216402212475", + "name": "servicios_pendiente", + "type": 6 + } + ], + "relations": [] + } + ], + "lastEntityId": "14:6869636155040222828", + "lastIndexId": "0:0", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [ + 8671758719262032228, + 6526292377427765903, + 6681525836793970759, + 7520878950629538641, + 1018517886500659076, + 3603523460509643630, + 7114932134790372235, + 3232242511921438300, + 1382612365248212275, + 3351796694055151590, + 6316955532661353856, + 7554667419929082362, + 7587010331144518890, + 8355522860524560355, + 8052528201055769722, + 585043060788268876, + 7737740310883011682, + 755391302787676104 + ], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/app/objectbox-models/default.json.bak b/app/objectbox-models/default.json.bak new file mode 100644 index 0000000..9f9bcf6 --- /dev/null +++ b/app/objectbox-models/default.json.bak @@ -0,0 +1,1148 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:5665101293431379024", + "lastPropertyId": "15:208191760973984819", + "name": "User", + "properties": [ + { + "id": "1:5321525569462566807", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:5712919134554378309", + "name": "email", + "type": 9 + }, + { + "id": "11:2978276056752430768", + "name": "nombre", + "type": 9 + }, + { + "id": "12:5508122438903068159", + "name": "apellido_paterno", + "type": 9 + }, + { + "id": "13:2241675499279008452", + "name": "apellido_materno", + "type": 9 + }, + { + "id": "14:8281964782181768919", + "name": "telefono", + "type": 9 + }, + { + "id": "15:208191760973984819", + "name": "tipo_empleado_id", + "type": 6 + } + ], + "relations": [] + }, + { + "id": "2:4372468123517637909", + "lastPropertyId": "16:1177766786685605044", + "name": "CheckListQuestion", + "properties": [ + { + "id": "1:3400228994932651467", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:8650406753973225582", + "name": "nombre", + "type": 9 + }, + { + "id": "6:1643483388542391711", + "name": "tipo", + "type": 9 + }, + { + "id": "10:8688927534108840815", + "name": "fecha", + "type": 9 + }, + { + "id": "11:2400276669962255845", + "name": "tipo_radio_btn", + "type": 5 + }, + { + "id": "12:6562151703877694550", + "name": "tipo_text", + "type": 5 + }, + { + "id": "13:602620057792595382", + "name": "tipo_checkbox", + "type": 5 + }, + { + "id": "14:2026798835348264681", + "name": "respuesta_radio_btn", + "type": 9 + }, + { + "id": "15:7423604211200632430", + "name": "respuesta_text", + "type": 9 + }, + { + "id": "16:1177766786685605044", + "name": "respuesta_checkbox", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "3:4377792362946807137", + "lastPropertyId": "2:670135943743083929", + "name": "Vehicle", + "properties": [ + { + "id": "1:4705575517894212104", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:670135943743083929", + "name": "nombre", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "4:9026394323396024465", + "lastPropertyId": "64:7631710765117535428", + "name": "Order", + "properties": [ + { + "id": "1:4790167818510396202", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:5822431497542564601", + "name": "idService", + "type": 6 + }, + { + "id": "3:368051226835786410", + "name": "idRequest", + "type": 6 + }, + { + "id": "4:5299115277318286719", + "name": "serviceName", + "type": 9 + }, + { + "id": "5:8509828799642636144", + "name": "idServiceStatus", + "type": 6 + }, + { + "id": "6:758228903305954550", + "name": "serviceStatusName", + "type": 9 + }, + { + "id": "7:3565593439842136067", + "name": "serviceStatusColor1", + "type": 9 + }, + { + "id": "8:1414901339937410644", + "name": "serviceStatusColor2", + "type": 9 + }, + { + "id": "9:766211398812222140", + "name": "idPayMethod", + "type": 6 + }, + { + "id": "10:5147361690223074723", + "name": "payMethodName", + "type": 9 + }, + { + "id": "11:4500258100657127431", + "name": "idServiceType", + "type": 6 + }, + { + "id": "12:8196932021437273167", + "name": "comments", + "type": 9 + }, + { + "id": "13:5865529873029354021", + "name": "cost", + "type": 9 + }, + { + "id": "14:4506031685435688370", + "name": "serviceTypeName", + "type": 9 + }, + { + "id": "15:8767456587919863166", + "name": "dateScheduled", + "type": 9 + }, + { + "id": "16:390465234933298875", + "name": "dateServiceRequest", + "type": 9 + }, + { + "id": "17:2232773430973000819", + "name": "idUserSchedule", + "type": 6 + }, + { + "id": "18:16974732538945366", + "name": "userScheduleName", + "type": 9 + }, + { + "id": "19:8555992252505178313", + "name": "userScheduleLastName", + "type": 9 + }, + { + "id": "20:6193392468828583706", + "name": "getUserScheduleMothersLastName", + "type": 9 + }, + { + "id": "21:4660796289579119889", + "name": "duracion", + "type": 9 + }, + { + "id": "22:3784527562475848084", + "name": "clientDefined", + "type": 6 + }, + { + "id": "23:622745567829234352", + "name": "idClient", + "type": 6 + }, + { + "id": "24:3488955318550079798", + "name": "clientDenomination", + "type": 9 + }, + { + "id": "25:1043990105761854142", + "name": "clientContactName", + "type": 9 + }, + { + "id": "26:2306458271923532534", + "name": "getClientContactCellphone", + "type": 9 + }, + { + "id": "27:8427015918175385673", + "name": "idClientAddress", + "type": 6 + }, + { + "id": "28:5613227685156295965", + "name": "streetAddress", + "type": 9 + }, + { + "id": "29:626834541446943773", + "name": "extNumberAddress", + "type": 9 + }, + { + "id": "30:3097111174238127635", + "name": "intNumberAddress", + "type": 9 + }, + { + "id": "31:2579197447570912033", + "name": "neighborhoodNameAddress", + "type": 9 + }, + { + "id": "32:8725626942246743515", + "name": "clientPostalCodeAddress", + "type": 9 + }, + { + "id": "33:7167249096611278992", + "name": "clientPhone", + "type": 9 + }, + { + "id": "34:1717487907822141185", + "name": "clientLat", + "type": 9 + }, + { + "id": "35:3923208556696213877", + "name": "clientLng", + "type": 9 + }, + { + "id": "36:2587769480300510869", + "name": "idOperator", + "type": 6 + }, + { + "id": "37:7098221334791768219", + "name": "operatorName", + "type": 9 + }, + { + "id": "38:2569311124845582577", + "name": "operatorLastName", + "type": 9 + }, + { + "id": "39:2199904261882308305", + "name": "operatorMothersName", + "type": 9 + }, + { + "id": "40:7770932078561947689", + "name": "idVehicle", + "type": 6 + }, + { + "id": "41:98427989004918261", + "name": "vehicleCodeName", + "type": 9 + }, + { + "id": "42:2451788547455986315", + "name": "vehicleOfficeName", + "type": 9 + }, + { + "id": "43:7831510545763858022", + "name": "idVehicleOffice", + "type": 6 + }, + { + "id": "44:8113716121116249544", + "name": "operatorOfficeName", + "type": 9 + }, + { + "id": "45:5857477213726475000", + "name": "idOperatorOffice", + "type": 6 + }, + { + "id": "46:1843499857429967030", + "name": "idAssistant1", + "type": 6 + }, + { + "id": "47:4275027260571981678", + "name": "idAssistant2", + "type": 6 + }, + { + "id": "48:7964662182008538023", + "name": "assistant1Name", + "type": 9 + }, + { + "id": "49:8566831797326428251", + "name": "assistant2Name", + "type": 9 + }, + { + "id": "50:9132735670991254606", + "name": "assistant1OfficeName", + "type": 9 + }, + { + "id": "51:1462071515349543367", + "name": "assistant2OfficeName", + "type": 9 + }, + { + "id": "52:3238931045124412371", + "name": "SurveyRequired", + "type": 5 + }, + { + "id": "53:2207836726115018568", + "name": "isSent", + "type": 1 + }, + { + "id": "57:2645779966994040528", + "name": "assistant1LastName", + "type": 9 + }, + { + "id": "58:3608510605234859928", + "name": "assistant1MothersName", + "type": 9 + }, + { + "id": "59:992561270306835037", + "name": "clientCity", + "type": 9 + }, + { + "id": "60:3211907636147776442", + "name": "negativeCost", + "type": 9 + }, + { + "id": "61:5525637547111531029", + "name": "pdfPath", + "type": 9 + }, + { + "id": "63:7095836277252619975", + "name": "sketchName", + "type": 9 + }, + { + "id": "64:7631710765117535428", + "name": "sketchPath", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "5:2910966717665207605", + "lastPropertyId": "2:359659400093111854", + "name": "NegativeServiceReason", + "properties": [ + { + "id": "1:8047838382872767093", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:359659400093111854", + "name": "descripcion", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "6:6355541814294916142", + "lastPropertyId": "5:1116445808186977890", + "name": "BusinessQuestion", + "properties": [ + { + "id": "1:9037400062941888607", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:231310261271246192", + "name": "nombre", + "type": 9 + }, + { + "id": "3:2611867171191406507", + "name": "orden", + "type": 5 + }, + { + "id": "4:4644663952015206374", + "name": "mostrar_numero", + "type": 5 + }, + { + "id": "5:1116445808186977890", + "name": "obligatorio", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "7:2859876280723130335", + "lastPropertyId": "5:8986637898173850281", + "name": "BusinessAnswer", + "properties": [ + { + "id": "1:1827935606743061935", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:8469863193806391937", + "name": "pregunta_id", + "type": 6 + }, + { + "id": "3:8600426673309812655", + "name": "nombre", + "type": 9 + }, + { + "id": "4:4263203625425115275", + "name": "orden", + "type": 5 + }, + { + "id": "5:8986637898173850281", + "name": "tipo_campo", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "8:6173653981125352089", + "lastPropertyId": "5:4524742060668120515", + "name": "DomesticAnswer", + "properties": [ + { + "id": "1:1246178104144041976", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:9022364118858654246", + "name": "pregunta_id", + "type": 6 + }, + { + "id": "3:1328613281292912347", + "name": "nombre", + "type": 9 + }, + { + "id": "4:6284466592499946392", + "name": "orden", + "type": 5 + }, + { + "id": "5:4524742060668120515", + "name": "tipo_campo", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "9:5835999145885427316", + "lastPropertyId": "5:7827790482540953055", + "name": "DomesticQuestion", + "properties": [ + { + "id": "1:7983234636383478791", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:3482813846550658400", + "name": "nombre", + "type": 9 + }, + { + "id": "3:9092368960227262920", + "name": "orden", + "type": 5 + }, + { + "id": "4:2553545940366448952", + "name": "mostrar_numero", + "type": 5 + }, + { + "id": "5:7827790482540953055", + "name": "obligatorio", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "10:5265773309037872977", + "lastPropertyId": "11:4204834214255679706", + "name": "Evidence", + "properties": [ + { + "id": "1:5097679613150378396", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:7852987476761877434", + "name": "evidenceNo", + "type": 6 + }, + { + "id": "3:6189591302802867443", + "name": "idOrder", + "type": 6 + }, + { + "id": "4:861441360886912220", + "name": "idRequest", + "type": 6 + }, + { + "id": "5:1047576737599164892", + "name": "type", + "type": 5 + }, + { + "id": "6:8546869156481956087", + "name": "path", + "type": 9 + }, + { + "id": "7:2483671667245694693", + "name": "lat", + "type": 9 + }, + { + "id": "8:6829820330214152801", + "name": "lng", + "type": 9 + }, + { + "id": "9:5161753908652537972", + "name": "viewRef", + "type": 5 + }, + { + "id": "10:2272502976273257358", + "name": "isSent", + "type": 1 + }, + { + "id": "11:4204834214255679706", + "name": "name", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "11:6475205932376807337", + "lastPropertyId": "53:1912263222050268605", + "name": "NextDayOrder", + "properties": [ + { + "id": "1:3264868661678103871", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:4456053841841817525", + "name": "idService", + "type": 6 + }, + { + "id": "3:7997711242381267225", + "name": "idRequest", + "type": 6 + }, + { + "id": "4:8271086619232851703", + "name": "serviceName", + "type": 9 + }, + { + "id": "5:7245352167591810796", + "name": "idServiceStatus", + "type": 6 + }, + { + "id": "6:5241727878047629545", + "name": "serviceStatusName", + "type": 9 + }, + { + "id": "7:4800279603731351311", + "name": "serviceStatusColor1", + "type": 9 + }, + { + "id": "8:1503098083362916520", + "name": "serviceStatusColor2", + "type": 9 + }, + { + "id": "9:4162194526312159096", + "name": "idPayMethod", + "type": 6 + }, + { + "id": "10:2343353027460528639", + "name": "payMethodName", + "type": 9 + }, + { + "id": "11:6561915191130038168", + "name": "idServiceType", + "type": 6 + }, + { + "id": "12:6839695974332536393", + "name": "comments", + "type": 9 + }, + { + "id": "13:1681165445808619896", + "name": "cost", + "type": 9 + }, + { + "id": "14:9201579815421133529", + "name": "serviceTypeName", + "type": 9 + }, + { + "id": "15:3532685957779838245", + "name": "dateScheduled", + "type": 9 + }, + { + "id": "16:1013511014762768893", + "name": "dateServiceRequest", + "type": 9 + }, + { + "id": "17:5141030834751053579", + "name": "idUserSchedule", + "type": 6 + }, + { + "id": "18:8316834618735938730", + "name": "userScheduleName", + "type": 9 + }, + { + "id": "19:3183152972501875602", + "name": "userScheduleLastName", + "type": 9 + }, + { + "id": "20:5109048459257258350", + "name": "getUserScheduleMothersLastName", + "type": 9 + }, + { + "id": "21:1871730937425078021", + "name": "duracion", + "type": 9 + }, + { + "id": "22:1907694265195596804", + "name": "clientDefined", + "type": 6 + }, + { + "id": "23:6625662055987934614", + "name": "idClient", + "type": 6 + }, + { + "id": "24:8843797071646769572", + "name": "clientDenomination", + "type": 9 + }, + { + "id": "25:9072820172183837569", + "name": "clientContactName", + "type": 9 + }, + { + "id": "26:6655742830533723645", + "name": "getClientContactCellphone", + "type": 9 + }, + { + "id": "27:4178398874819395513", + "name": "idClientAddress", + "type": 6 + }, + { + "id": "28:684820188619097939", + "name": "streetAddress", + "type": 9 + }, + { + "id": "29:8119650734803796332", + "name": "extNumberAddress", + "type": 9 + }, + { + "id": "30:8725509132974294337", + "name": "intNumberAddress", + "type": 9 + }, + { + "id": "31:559116993585388728", + "name": "neighborhoodNameAddress", + "type": 9 + }, + { + "id": "32:722618039033631165", + "name": "clientPostalCodeAddress", + "type": 9 + }, + { + "id": "33:5122355857579820755", + "name": "clientPhone", + "type": 9 + }, + { + "id": "34:5061809862649615222", + "name": "clientLat", + "type": 9 + }, + { + "id": "35:4421805919905742210", + "name": "clientLng", + "type": 9 + }, + { + "id": "36:16208392747324573", + "name": "idOperator", + "type": 6 + }, + { + "id": "37:1010399692004420493", + "name": "operatorName", + "type": 9 + }, + { + "id": "38:3377567735585312268", + "name": "operatorLastName", + "type": 9 + }, + { + "id": "39:3462303112734700369", + "name": "operatorMothersName", + "type": 9 + }, + { + "id": "40:8652651578878147290", + "name": "idVehicle", + "type": 6 + }, + { + "id": "41:7910381791883008711", + "name": "vehicleCodeName", + "type": 9 + }, + { + "id": "42:9132815036783490247", + "name": "vehicleOfficeName", + "type": 9 + }, + { + "id": "43:7372464010650191612", + "name": "idVehicleOffice", + "type": 6 + }, + { + "id": "44:1418155582741812092", + "name": "operatorOfficeName", + "type": 9 + }, + { + "id": "45:1573622015946405668", + "name": "idOperatorOffice", + "type": 6 + }, + { + "id": "46:8767471268383720558", + "name": "idAssistant1", + "type": 6 + }, + { + "id": "47:3801283899175927660", + "name": "idAssistant2", + "type": 6 + }, + { + "id": "48:4218765227230057063", + "name": "assistant1Name", + "type": 9 + }, + { + "id": "49:4638061793666351178", + "name": "assistant2Name", + "type": 9 + }, + { + "id": "50:7070724765117443211", + "name": "assistant1OfficeName", + "type": 9 + }, + { + "id": "51:5901287936339728517", + "name": "assistant2OfficeName", + "type": 9 + }, + { + "id": "52:3442054022641070646", + "name": "SurveyRequired", + "type": 5 + }, + { + "id": "53:1912263222050268605", + "name": "isSent", + "type": 1 + } + ], + "relations": [] + }, + { + "id": "12:6957973966007774656", + "lastPropertyId": "21:4053300102365953297", + "name": "OrderProgress", + "properties": [ + { + "id": "1:4909409867696772588", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:2376195153552155877", + "name": "idRequest", + "type": 6 + }, + { + "id": "3:8737352292837009750", + "name": "startDate", + "type": 9 + }, + { + "id": "4:8420872139229865602", + "name": "endDate", + "type": 9 + }, + { + "id": "5:4280475918090828107", + "name": "comments", + "type": 9 + }, + { + "id": "6:1833202438305001872", + "name": "elapsedTime", + "type": 9 + }, + { + "id": "7:4615340923415236343", + "name": "startLat", + "type": 9 + }, + { + "id": "8:1860619079461141044", + "name": "startLng", + "type": 9 + }, + { + "id": "9:7560964551558538330", + "name": "endLat", + "type": 9 + }, + { + "id": "10:3899822933000985859", + "name": "endLng", + "type": 9 + }, + { + "id": "11:7652484697176584591", + "name": "signaturePath", + "type": 9 + }, + { + "id": "12:6492043671717914407", + "name": "warranty", + "type": 1 + }, + { + "id": "13:2408814832806683450", + "name": "isSent", + "type": 1 + }, + { + "id": "14:7034569438693192646", + "name": "shouldBeSent", + "type": 1 + }, + { + "id": "15:2804509603161477239", + "name": "signatureSent", + "type": 1 + }, + { + "id": "16:4180953079476690512", + "name": "isOnSurvey", + "type": 1 + }, + { + "id": "17:3644568522287365574", + "name": "shouldSendSurvey", + "type": 1 + }, + { + "id": "18:6301736258721676414", + "name": "surveySent", + "type": 1 + }, + { + "id": "19:7669451713533174054", + "name": "liters", + "type": 5 + }, + { + "id": "20:3018195537892130994", + "name": "negativeService", + "type": 5 + }, + { + "id": "21:4053300102365953297", + "name": "signatureName", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "13:1724014081733562326", + "lastPropertyId": "5:7510073138677565100", + "name": "SavedAnswer", + "properties": [ + { + "id": "1:1211012512255883947", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:4555912609217419213", + "name": "questionId", + "type": 6 + }, + { + "id": "3:4690130029848358340", + "name": "orderId", + "type": 6 + }, + { + "id": "4:1609083068078937709", + "name": "isOpen", + "type": 1 + }, + { + "id": "5:7510073138677565100", + "name": "answer", + "type": 9 + } + ], + "relations": [] + }, + { + "id": "14:6869636155040222828", + "lastPropertyId": "6:4385486216402212475", + "name": "Operator", + "properties": [ + { + "id": "1:8291666170904199724", + "name": "id", + "type": 6, + "flags": 129 + }, + { + "id": "2:8545676381506904920", + "name": "nombre", + "type": 9 + }, + { + "id": "3:3719435367169286614", + "name": "apellido_paterno", + "type": 9 + }, + { + "id": "4:878061371212085518", + "name": "apellido_materno", + "type": 9 + }, + { + "id": "5:1604294184145832145", + "name": "servicios_total", + "type": 6 + }, + { + "id": "6:4385486216402212475", + "name": "servicios_pendiente", + "type": 6 + } + ], + "relations": [] + } + ], + "lastEntityId": "14:6869636155040222828", + "lastIndexId": "0:0", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [ + 8671758719262032228, + 6526292377427765903, + 6681525836793970759, + 7520878950629538641, + 1018517886500659076, + 3603523460509643630, + 7114932134790372235, + 3232242511921438300, + 1382612365248212275, + 3351796694055151590, + 6316955532661353856, + 7554667419929082362, + 7587010331144518890, + 8355522860524560355, + 8052528201055769722, + 585043060788268876, + 7737740310883011682, + 755391302787676104 + ], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/iesoluciones/siodrenax/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/iesoluciones/siodrenax/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..e6a82d8 --- /dev/null +++ b/app/src/androidTest/java/com/iesoluciones/siodrenax/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.iesoluciones.siodrenax + +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.iesoluciones.siodrenax", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0913d12 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/images/logo_drenax.png b/app/src/main/assets/images/logo_drenax.png new file mode 100644 index 0000000..1806c3c Binary files /dev/null and b/app/src/main/assets/images/logo_drenax.png differ diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..25737f2 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/iesoluciones/siodrenax/App.kt b/app/src/main/java/com/iesoluciones/siodrenax/App.kt new file mode 100644 index 0000000..49816ff --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/App.kt @@ -0,0 +1,116 @@ +package com.iesoluciones.siodrenax + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context +import android.util.Log +import com.iesoluciones.siodrenax.BuildConfig.BASE_URL +import com.iesoluciones.siodrenax.entities.* +import com.iesoluciones.siodrenax.network.Api +import com.iesoluciones.siodrenax.network.TokenInterceptor +import io.objectbox.Box +import io.objectbox.BoxStore +import io.objectbox.android.AndroidObjectBrowser +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +val preferencesHelper: PreferencesHelper by lazy { App.prefs!! } +val api: Api by lazy { App.api!! } +val userBox: Box by lazy { App.userBox!! } +val checkListQuestionBox: Box by lazy { App.checkListQuestionBox!! } +val vehicleBox: Box by lazy { App.vehicleBox!! } +val orderBox: Box by lazy { App.orderBox!! } +val orderProgressBox: Box by lazy { App.orderProgressBox!! } +val evidenceBox: Box by lazy { App.evidenceBox!! } +val businessQuestionBox: Box by lazy { App.businessQuestionBox!! } +val domesticQuestionBox: Box by lazy { App.domesticQuestionBox!! } +val negativeServiceReasonBox: Box by lazy { App.negativeServiceReasonBox!! } +val businessAnswerBox: Box by lazy { App.businessAnswerBox!! } +val domesticAnswerBox: Box by lazy { App.domesticAnswerBox!! } +val savedAnswerBox: Box by lazy { App.savedAnswerBox!! } +val operatorBox: Box by lazy {App.operatorBox!!} +val nextDayOrderBox: Box by lazy {App.nextDayOrderBox!!} + +class App : Application() { + + companion object { + var prefs: PreferencesHelper? = null + var api: Api? = null + var context: Context? = null + var shareInstance: App? = null + var boxStore: BoxStore? = null + var userBox: Box? = null + var checkListQuestionBox: Box? = null + var vehicleBox: Box? = null + var orderBox: Box? = null + var orderProgressBox: Box? = null + var evidenceBox: Box? = null + var businessQuestionBox: Box? = null + var domesticQuestionBox: Box? = null + var negativeServiceReasonBox: Box? = null + var businessAnswerBox: Box? = null + var domesticAnswerBox: Box? = null + var savedAnswerBox: Box? = null + var operatorBox: Box? = null + var nextDayOrderBox: Box? = null + } + + @SuppressLint("CheckResult") + override fun onCreate() { + super.onCreate() + + prefs = PreferencesHelper(applicationContext) + shareInstance = this + context = this + + boxStore = MyObjectBox.builder().androidContext(this).build() + userBox = boxStore!!.boxFor(User::class.java) + checkListQuestionBox = boxStore!!.boxFor(CheckListQuestion::class.java) + vehicleBox = boxStore!!.boxFor(Vehicle::class.java) + orderBox = boxStore!!.boxFor(Order::class.java) + orderProgressBox = boxStore!!.boxFor(OrderProgress::class.java) + evidenceBox = boxStore!!.boxFor(Evidence::class.java) + businessQuestionBox = boxStore!!.boxFor(BusinessQuestion::class.java) + domesticQuestionBox = boxStore!!.boxFor(DomesticQuestion::class.java) + negativeServiceReasonBox = boxStore!!.boxFor(NegativeServiceReason::class.java) + businessAnswerBox = boxStore!!.boxFor(BusinessAnswer::class.java) + domesticAnswerBox = boxStore!!.boxFor(DomesticAnswer::class.java) + savedAnswerBox = boxStore!!.boxFor(SavedAnswer::class.java) + operatorBox = boxStore!!.boxFor(Operator::class.java) + nextDayOrderBox = boxStore!!.boxFor(NextDayOrder::class.java) + + //Starting ObjectBox Data Browser (ONLY FOR TEST!!!) + // TODO Comentar antes de liberar + if (BuildConfig.DEBUG) { + val started = AndroidObjectBrowser(boxStore).start(this) + Log.i("ObjectBrowser", "Started: $started") + } + + //Instantiate Logging interceptor + val logging = HttpLoggingInterceptor() + logging.level = HttpLoggingInterceptor.Level.BASIC + + //Build HTTP Client + val client = OkHttpClient.Builder() + .addInterceptor(TokenInterceptor()) + .addInterceptor(logging) + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .build() + + //Build Retrofit Client + val retrofit = Retrofit.Builder() + .client(client) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl(BASE_URL) + .build() + + api = retrofit.create(Api::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/PreferencesHelper.kt b/app/src/main/java/com/iesoluciones/siodrenax/PreferencesHelper.kt new file mode 100644 index 0000000..8e7c7f6 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/PreferencesHelper.kt @@ -0,0 +1,65 @@ +package com.iesoluciones.siodrenax + +import android.content.Context + +class PreferencesHelper(context: Context) { + + private val fileName = "share_preferences_file" + private val prefs = context.getSharedPreferences(fileName, Context.MODE_PRIVATE) + + private val TOKEN_API = "tokenApi" + private val TOKEN_FIREBASE = "tokenFirebase" + private val IS_MANAGER = "isManager" + private val CHECK_LIST_PROGRESS = "checkListProgress" + private val IS_ENABLE_HERRAMIENTA_SURVEY = "isEnableHerramientaSurvey" + private val WORKDAY_STARTED = "workday_started" + private val WORKDAY_ID = "workday_id" + private val GPS_INTERVAL_IN_MINUTES = "gps_interval" + private val ORDER_PROGRESS = "order_in_progress" + private val IS_ENABLE_TO_SYNC = "syncEnable" + + var tokenApi: String? + get() = prefs.getString(TOKEN_API, null) + set(value) = prefs.edit().putString(TOKEN_API, value).apply() + + var tokenFirebase: String? + get() = prefs.getString(TOKEN_FIREBASE, "default_token_firebase") + set(value) = prefs.edit().putString(TOKEN_FIREBASE, value).apply() + + var isManager: Boolean + get() = prefs.getBoolean(IS_MANAGER, false) + set(value) = prefs.edit().putBoolean(IS_MANAGER, value).apply() + + var checkListProgress: String? + get() = prefs.getString(CHECK_LIST_PROGRESS, null) + set(value) = prefs.edit().putString(CHECK_LIST_PROGRESS, value).apply() + + var isEnableHerramientaSurvey: Boolean + get() = prefs.getBoolean(IS_ENABLE_HERRAMIENTA_SURVEY, true) + set(value) = prefs.edit().putBoolean(IS_ENABLE_HERRAMIENTA_SURVEY, value).apply() + + var workdayStarted: Boolean + get() = prefs.getBoolean(WORKDAY_STARTED, false) + set(value) = prefs.edit().putBoolean(WORKDAY_STARTED, value).apply() + + var workdayId: Int + get() = prefs.getInt(WORKDAY_ID, 0) + set(value) = prefs.edit().putInt(WORKDAY_ID, value).apply() + + var gpsInterval: Int + get() = prefs.getInt(GPS_INTERVAL_IN_MINUTES, 3) + set(value) = prefs.edit().putInt(GPS_INTERVAL_IN_MINUTES, value).apply() + + var orderInProgress: Long? + get() = prefs.getLong(ORDER_PROGRESS, 0L) + set(value){ + if (value == null) + prefs.edit().remove(ORDER_PROGRESS).apply() + else + prefs.edit().putLong(ORDER_PROGRESS, value).apply() + } + + var isEnableToSync: Boolean + get() = prefs.getBoolean(IS_ENABLE_TO_SYNC, true) + set(value) = prefs.edit().putBoolean(IS_ENABLE_TO_SYNC, value).apply() +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/CameraActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/CameraActivity.kt new file mode 100644 index 0000000..1548b72 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/CameraActivity.kt @@ -0,0 +1,262 @@ +package com.iesoluciones.siodrenax.activities + +import android.animation.Animator +import android.graphics.Bitmap +import android.os.Bundle +import android.view.View +import android.view.animation.DecelerateInterpolator +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ActivityCameraBinding +import com.iesoluciones.siodrenax.entities.Evidence +import com.iesoluciones.siodrenax.repositories.EvidenceRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LATITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LONGITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCIA +import com.iesoluciones.siodrenax.utils.Constants.Companion.HIDE_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.HIDE_DURATION +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_DURATION +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_TITLE_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_TITLE_DELAY +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_TITLE_DURATION +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.toastLong +import com.iesoluciones.siodrenax.viewmodels.EvidenceViewModel +import io.fotoapparat.Fotoapparat +import io.fotoapparat.configuration.UpdateConfiguration +import io.fotoapparat.error.CameraErrorListener +import io.fotoapparat.exception.camera.CameraException +import io.fotoapparat.log.fileLogger +import io.fotoapparat.log.logcat +import io.fotoapparat.log.loggers +import io.fotoapparat.parameter.ScaleType +import io.fotoapparat.result.BitmapPhoto +import io.fotoapparat.result.WhenDoneListener +import io.fotoapparat.selector.back +import io.fotoapparat.selector.off +import io.fotoapparat.selector.torch + +class CameraActivity : AppCompatActivity() { + + private var resultado: Int = RESULT_OK + private var torchOn = false + private var show = true + + private lateinit var binding: ActivityCameraBinding + private lateinit var evidenceViewModel: EvidenceViewModel + private lateinit var evidenceRepository: EvidenceRepository + private lateinit var evidence: Evidence + private lateinit var fotoapparat: Fotoapparat + private var bitmapResult: Bitmap? = null + private lateinit var animListenerHide: Animator.AnimatorListener + private lateinit var animListenerShow: Animator.AnimatorListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityCameraBinding.inflate(layoutInflater) + setContentView(binding.root) + + evidenceViewModel = ViewModelProvider(this)[EvidenceViewModel::class.java] + evidenceRepository = EvidenceRepository() + + binding.cameraView.visibility = View.VISIBLE + evidence = evidenceRepository.getEvidenceById(intent.getLongExtra(EVIDENCE_ID, 0L)) + + showTitle(EVIDENCIA + evidence.getTypeDescription() + " " + evidence.evidenceNo) + fotoapparat = createFotoapparat() + + binding.capture.setOnClickListener { takePicture() } + binding.ivTorch.setImageResource(R.drawable.torch_off) + binding.ivTorch.setOnClickListener { toggleTorchOnImageView() } + binding.buttonConfirm.setOnClickListener { confirm() } + binding.buttonCancel.setOnClickListener { cancel() } + binding.tvCerrar.setOnClickListener { + fotoapparat.stop() + finish() + } + prepareGuidelines() + attachObservers() + } + + override fun onStart() { + super.onStart() + fotoapparat.start() + } + + override fun onStop() { + super.onStop() + fotoapparat.stop() + finish() + } + + override fun finish() { + setResult(resultado) + super.finish() + } + + private fun createFotoapparat(): Fotoapparat { + return Fotoapparat.with(this) + .into(binding.cameraView) + .previewScaleType(ScaleType.CenterInside) + .lensPosition(back()) + .logger(loggers(logcat(), fileLogger(this))) + .cameraErrorCallback(object : CameraErrorListener { + override fun onError(e: CameraException) { + toastLong(e.toString()) + } + }) + .build() + } + + private fun toggleTorchOnImageView() { + torchOn = !torchOn + binding.ivTorch.setImageResource(if (torchOn) R.drawable.torch else R.drawable.torch_off) + fotoapparat.updateConfiguration( + UpdateConfiguration.builder() + .flash(if (torchOn) torch() else off()) + .build() + ) + } + + private fun takePicture() { + binding.capture.isEnabled = false + binding.frameLoading.visibility = View.VISIBLE + + val photoResult = fotoapparat.takePicture() + + photoResult + .toBitmap() + .whenDone(object : WhenDoneListener { + override fun whenDone(it: BitmapPhoto?) { + runOnUiThread { + if (it != null) { + binding.frameContent.visibility = View.VISIBLE + binding.tvDescription.text = getString( + R.string.evidence_description, + EVIDENCIA, + evidence.getTypeDescription(), + evidence.evidenceNo + ) + + bitmapResult = HelperUtil().scaleBitmap(it.bitmap, binding.root.context.resources.displayMetrics.widthPixels) + binding.touchImageView.setImageBitmap(bitmapResult) + binding.frameLoading.visibility = View.GONE + binding.capture.isEnabled = true + } + } + } + }) + } + + private fun prepareGuidelines() { + animListenerHide = object : Animator.AnimatorListener { + override fun onAnimationStart(animator: Animator) {} + override fun onAnimationEnd(animator: Animator) { + binding.tvCerrar.visibility = View.GONE + binding.relativeFullScreen.visibility = View.GONE + } + + override fun onAnimationCancel(animator: Animator) {} + override fun onAnimationRepeat(animator: Animator) {} + } + + animListenerShow = object : Animator.AnimatorListener { + override fun onAnimationStart(animator: Animator) { + binding.tvCerrar.visibility = View.VISIBLE + binding.relativeFullScreen.visibility = View.VISIBLE + } + + override fun onAnimationEnd(animator: Animator) {} + override fun onAnimationCancel(animator: Animator) {} + override fun onAnimationRepeat(animator: Animator) {} + } + + binding.touchImageView.setOnClickListener { + show = !show + if (show) + show() + else + hide() + } + } + + private fun hide() { + binding.relativeFullScreen.animate() + .alpha(HIDE_ALPHA) + .setDuration(HIDE_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerHide) + .start() + + binding.tvCerrar.animate() + .alpha(HIDE_ALPHA) + .setDuration(HIDE_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerHide) + .start() + } + + private fun show() { + binding.relativeFullScreen.animate() + .alpha(SHOW_ALPHA) + .setDuration(SHOW_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerShow) + .start() + + binding.tvCerrar.animate() + .alpha(SHOW_ALPHA) + .setDuration(SHOW_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerShow) + .start() + } + + private fun showTitle(title: String?) { + binding.cardTitle.visibility = View.VISIBLE + binding.tvTitle.text = title + binding.cardTitle.alpha = SHOW_ALPHA + binding.cardTitle.animate() + .alpha(SHOW_TITLE_ALPHA) + .setDuration(SHOW_TITLE_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setStartDelay(SHOW_TITLE_DELAY) + .start() + } + + private fun confirm() { + if (bitmapResult != null) { + binding.buttonConfirm.visibility = View.GONE + binding.buttonCancel.visibility = View.GONE + binding.buttonConfirm.isEnabled = false + evidenceViewModel.saveImage(filesDir, bitmapResult!!) + } + } + + private fun cancel() { + bitmapResult = null + binding.touchImageView.setImageBitmap(null) + binding.frameContent.visibility = View.GONE + } + + private fun attachObservers() { + evidenceViewModel.onImageSaved.observe(this) { evidenceSignatureLocal -> + binding.buttonConfirm.isEnabled = true + + if (evidenceSignatureLocal.path != null && evidenceSignatureLocal.name != null) { + evidence.path = evidenceSignatureLocal.path + evidence.name = evidenceSignatureLocal.name + evidence.lat = DEFAULT_LATITUDE.toString() + evidence.lng = DEFAULT_LONGITUDE.toString() + evidenceRepository.saveEvidence(evidence) + } else { + resultado = RESULT_CANCELED + } + + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/ConfirmationActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/ConfirmationActivity.kt new file mode 100644 index 0000000..8a23180 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/ConfirmationActivity.kt @@ -0,0 +1,157 @@ +package com.iesoluciones.siodrenax.activities + +import android.animation.Animator +import android.graphics.Bitmap +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import android.view.animation.DecelerateInterpolator +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ActivityConfirmationBinding +import com.iesoluciones.siodrenax.entities.Evidence +import com.iesoluciones.siodrenax.repositories.EvidenceRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCIA +import com.iesoluciones.siodrenax.utils.Constants.Companion.HIDE_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.HIDE_DURATION +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.SHOW_DURATION +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.TouchImageView +import com.iesoluciones.siodrenax.utils.toastLong + +class ConfirmationActivity : AppCompatActivity(), View.OnClickListener { + + private var show = true + private var bitmap: Bitmap? = null + private lateinit var evidence: Evidence + private lateinit var evidenceRepository: EvidenceRepository + private lateinit var animListenerHide: Animator.AnimatorListener + private lateinit var animListenerShow: Animator.AnimatorListener + private lateinit var binding: ActivityConfirmationBinding + + private lateinit var tvDescription: TextView + private lateinit var tvCerrar: TextView + private lateinit var touchImageView: TouchImageView + private lateinit var relativeFullScreen: RelativeLayout + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //window.setDecorFitsSystemWindows(false) + + binding = ActivityConfirmationBinding.inflate(layoutInflater) + setContentView(binding.root) + viewBinding() + + evidenceRepository = EvidenceRepository() + evidence = evidenceRepository.getEvidenceById( + intent.getLongExtra(EVIDENCE_ID, 0L) + ) + + try { + bitmap = HelperUtil().decodeFromFile(evidence.path!!, 0) + touchImageView.drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH + touchImageView.setImageBitmap(bitmap) + } catch (outOfMemoryError: OutOfMemoryError) { + toastLong(resources.getString(R.string.out_of_memory_error)) + } + + animListenerHide = object : Animator.AnimatorListener { + override fun onAnimationStart(animator: Animator) {} + override fun onAnimationEnd(animator: Animator) { + tvCerrar.visibility = View.GONE + relativeFullScreen.visibility = View.GONE + } + + override fun onAnimationCancel(animator: Animator) {} + override fun onAnimationRepeat(animator: Animator) {} + } + + animListenerShow = object : Animator.AnimatorListener { + override fun onAnimationStart(animator: Animator) { + tvCerrar.visibility = View.VISIBLE + relativeFullScreen.visibility = View.VISIBLE + } + + override fun onAnimationEnd(animator: Animator) {} + override fun onAnimationCancel(animator: Animator) {} + override fun onAnimationRepeat(animator: Animator) {} + } + + tvCerrar.setOnClickListener(this) + tvCerrar.visibility = View.VISIBLE + val description = EVIDENCIA + evidence.getTypeDescription() + " " + evidence.evidenceNo + tvDescription.text = description + + touchImageView.setOnClickListener { + show = !show + if (show) show() else hide() + } + } + + override fun onDestroy() { + super.onDestroy() + bitmap?.recycle() + } + + override fun onClick(v: View?) { + if (v != null) { + when (v.id) { + R.id.tvCerrar -> onBackPressed() + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun hide() { + relativeFullScreen.animate() + .alpha(HIDE_ALPHA) + .setDuration(HIDE_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerHide) + .start() + + tvCerrar.animate() + .alpha(HIDE_ALPHA) + .setDuration(HIDE_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerHide) + .start() + } + + private fun show() { + relativeFullScreen.animate() + .alpha(SHOW_ALPHA) + .setDuration(SHOW_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerShow) + .start() + + tvCerrar.animate() + .alpha(SHOW_ALPHA) + .setDuration(SHOW_DURATION) + .setInterpolator(DecelerateInterpolator()) + .setListener(animListenerShow) + .start() + } + + private fun viewBinding(){ + tvDescription = binding.tvDescription + tvCerrar = binding.tvCerrar + touchImageView = binding.touchImageView + relativeFullScreen = binding.relativeFullScreen + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/HerramientaSurveyActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/HerramientaSurveyActivity.kt new file mode 100644 index 0000000..9a56705 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/HerramientaSurveyActivity.kt @@ -0,0 +1,112 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.HerramientaSurveyAdapter +import com.iesoluciones.siodrenax.databinding.ActivityHerramientaSurveyBinding +import com.iesoluciones.siodrenax.entities.CheckListQuestion +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.CheckListRepository +import com.iesoluciones.siodrenax.utils.Constants +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration +import com.iesoluciones.siodrenax.viewmodels.CheckListViewModel + +class HerramientaSurveyActivity : ToolbarActivity() { + + private lateinit var checkListRepository: CheckListRepository + private lateinit var checkListQuestion: List + private lateinit var adapter: HerramientaSurveyAdapter + private lateinit var binding: ActivityHerramientaSurveyBinding + private lateinit var checkListViewModel: CheckListViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + + super.onCreate(savedInstanceState) + binding = ActivityHerramientaSurveyBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.herramienta) + toolbarToLoad(toolbar) + + checkListRepository = CheckListRepository() + checkListQuestion = checkListRepository.getSurveyCheckList(Constants.HERRAMIENTA) + + val recyclerView = binding.recycler + recyclerView.visibility = View.VISIBLE + recyclerView.addItemDecoration(ListPaddingDecoration(this@HerramientaSurveyActivity)) + + adapter = HerramientaSurveyAdapter(checkListQuestion) + recyclerView.adapter = adapter + recyclerView.layoutManager = LinearLayoutManager(this@HerramientaSurveyActivity) + + checkListViewModel = ViewModelProvider(this)[CheckListViewModel::class.java] + + attachObservers() + + binding.btnSiguiente.text = getString(R.string.hold_to_finish) + + binding.btnSiguiente.setOnClickListener { finalizarEncuesta() } + } + + private fun finalizarEncuesta() { + AlertDialog.Builder(this@HerramientaSurveyActivity) + .setTitle(R.string.titleDialogEncuestaOperadorFinalizar) + .setMessage(R.string.messageDialogEncuestaOperadorFinalizar) + .setPositiveButton(R.string.accept) { _, _ -> checkListViewModel.checkList() } + .setNegativeButton(R.string.cancel, null) + .show() + } + + private fun attachObservers() { + checkListViewModel.isLoading.observe(this) { s -> + if (s != null) { + binding.btnSiguiente.visibility = View.GONE + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + } else { + binding.btnSiguiente.isEnabled = true + binding.frameLoading.visibility = View.GONE + } + } + + checkListViewModel.checkListSuccess.observe(this) { checkResponse -> + if (checkResponse != null) { + preferencesHelper.checkListProgress = Constants.WORKDAY + val intent = Intent(this@HerramientaSurveyActivity, WorkdayActivity::class.java) + startActivity(intent) + finish() + } + } + + checkListViewModel.checkListFailure.observe(this) { throwable -> + binding.btnSiguiente.visibility = View.VISIBLE + if (throwable != null) { + disableSurvey() + HelperUtil().parseError(this, throwable) + checkListViewModel.checkListFailure.postValue(null) + } + } + } + + private fun disableSurvey() { + if (!preferencesHelper.isEnableHerramientaSurvey) + return + + preferencesHelper.isEnableHerramientaSurvey = false + + val count = checkListQuestion.count() + for (position in 0 until count) { + adapter.notifyItemChanged(position) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/LoginActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/LoginActivity.kt new file mode 100644 index 0000000..9be8a84 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/LoginActivity.kt @@ -0,0 +1,119 @@ +package com.iesoluciones.siodrenax.activities + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.provider.Settings +import android.util.Log +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import com.google.android.gms.tasks.Task +import com.google.firebase.messaging.FirebaseMessaging +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ActivityLoginBinding +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.CheckListRepository +import com.iesoluciones.siodrenax.utils.* +import com.iesoluciones.siodrenax.viewmodels.LoginViewModel +import java.io.File + +class LoginActivity : AppCompatActivity() { + + private lateinit var binding: ActivityLoginBinding + private lateinit var loginViewModel: LoginViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) + + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + attachObservers() + + binding.btnLogin.setOnClickListener { login() } + + //binding.etEmail.setText("operador@mail.com") + //binding.etPassword.setText("secret") + + firebaseNotifications() + + deleteSketchFolder() + } + + @SuppressLint("HardwareIds") + private fun login() { + val email = binding.etEmail.text.toString() + val password = binding.etPassword.text.toString() + + if (email.isNotEmpty() && password.isNotEmpty()) { + val deviceId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) + loginViewModel.login(email, password, deviceId) + } else if (email.isEmpty()) { + toast(getString(R.string.required_email)) + } else if (password.isEmpty()) { + toast(getString(R.string.required_password)) + } + } + + private fun attachObservers() { + + loginViewModel.isLoading.observe(this) { s -> + if (s != null) { + binding.btnLogin.isEnabled = false + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + } else { + binding.btnLogin.isEnabled = true + binding.frameLoading.visibility = View.GONE + } + } + + loginViewModel.loginSuccess.observe(this) { success -> + if (success) { + + if (preferencesHelper.isManager) { + goToActivity { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + finish() + } else if (!preferencesHelper.isManager) { + var progressActivity: Class<*> = WorkdayActivity::class.java + + if (CheckListRepository().isCheckListDone()) + progressActivity = RevisionSurveyActivity::class.java + + val intent = Intent(this, progressActivity) + startActivity(intent) + finish() + } + } + } + + loginViewModel.loginFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this, throwable) + loginViewModel.loginFailure.postValue(null) + } + } + } + + private fun firebaseNotifications() { + FirebaseMessaging.getInstance().token.addOnCompleteListener { task: Task -> + if (!task.isSuccessful) { + Log.w("FIREBASE", getString(R.string.firebase_token_registration_failed), task.exception) + return@addOnCompleteListener + } + // Get new FCM registration token + preferencesHelper.tokenFirebase = task.result!! + } + } + + private fun deleteSketchFolder(){ + try { + File("$filesDir","pdf_sketchs").deleteRecursively() + } catch (e: Exception) { + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/MaterialSurveyActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/MaterialSurveyActivity.kt new file mode 100644 index 0000000..0c50bed --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/MaterialSurveyActivity.kt @@ -0,0 +1,61 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.MaterialSurveyAdapter +import com.iesoluciones.siodrenax.databinding.ActivityMaterialSurveyBinding +import com.iesoluciones.siodrenax.entities.CheckListQuestion +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.CheckListRepository +import com.iesoluciones.siodrenax.utils.Constants +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration + +class MaterialSurveyActivity : ToolbarActivity() { + + private lateinit var checkListRepository: CheckListRepository + private lateinit var checkListQuestion: List + lateinit var binding: ActivityMaterialSurveyBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMaterialSurveyBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.material) + toolbarToLoad(toolbar) + + checkListRepository = CheckListRepository() + checkListQuestion = checkListRepository.getSurveyCheckList(Constants.MATERIAL) + + val recyclerView = binding.recycler + recyclerView.visibility = View.VISIBLE + recyclerView.addItemDecoration(ListPaddingDecoration(this@MaterialSurveyActivity)) + recyclerView.adapter = MaterialSurveyAdapter(checkListQuestion) + recyclerView.layoutManager = LinearLayoutManager(this@MaterialSurveyActivity) + + binding.btnSiguiente.text = getString(R.string.siguiente) + binding.btnSiguiente.setOnClickListener { onClickBtnSiguiente() } + } + + private fun onClickBtnSiguiente() { + AlertDialog.Builder(this@MaterialSurveyActivity) + .setTitle(R.string.titleDialogEncuestaOperador) + .setMessage(R.string.messageDialogEncuestaOperador) + .setPositiveButton(R.string.accept) { _, _ -> + preferencesHelper.checkListProgress = Constants.HERRAMIENTA + val intent = Intent(this@MaterialSurveyActivity, HerramientaSurveyActivity::class.java) + startActivity(intent) + finish() + } + .setNegativeButton(R.string.cancel, null) + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/NextServiceActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/NextServiceActivity.kt new file mode 100644 index 0000000..7b0d2ef --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/NextServiceActivity.kt @@ -0,0 +1,126 @@ +package com.iesoluciones.siodrenax.activities + +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import android.widget.TextView +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.NextDayOrdersAdapter +import com.iesoluciones.siodrenax.databinding.ActivityNextServiceBinding +import com.iesoluciones.siodrenax.entities.NextDayOrder +import com.iesoluciones.siodrenax.receivers.UpdateUIReceiver +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration +import com.iesoluciones.siodrenax.viewmodels.OrdersViewModel + +class NextServiceActivity : ToolbarActivity(), SwipeRefreshLayout.OnRefreshListener, + UpdateUIReceiver.UiUpdateListener { + + private lateinit var binding: ActivityNextServiceBinding + private lateinit var ordersViewModel: OrdersViewModel + + private lateinit var orderList: List + private var adapter: NextDayOrdersAdapter? = null + + private lateinit var recyclerView: RecyclerView + private lateinit var swipeRefresh: SwipeRefreshLayout + private lateinit var tvEmpty: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityNextServiceBinding.inflate(layoutInflater) + setContentView(binding.root) + viewBinding() + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.next_day_services) + toolbarToLoad(toolbar) + + ordersViewModel = ViewModelProvider(this)[OrdersViewModel::class.java] + + attachToObservables() + swipeRefresh.setOnRefreshListener(this) + } + + override fun onRefresh() { + ordersViewModel.isLoading.value = getString(R.string.loading) + ordersViewModel.refreshNextDayOrders() + } + + override fun updateUI() {} + + override fun onResume() { + super.onResume() + ordersViewModel.updateNextDayOrdersFromDB() + } + + override fun onDestroy() { + super.onDestroy() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + + override fun onBackPressed() { + super.onBackPressed() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.slide_out_right_airbnb) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + finish() + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.slide_out_right_airbnb + ) + } + } + + return super.onOptionsItemSelected(item) + } + + override fun onPointerCaptureChanged(hasCapture: Boolean) {} + + private fun attachToObservables() { + ordersViewModel.refreshNextDayOrders() + ordersViewModel.getNextDayOrders().observe(this) { nextDayOrders -> + orderList = nextDayOrders + if (nextDayOrders.isNotEmpty()) { + if (adapter == null) { + tvEmpty.visibility = View.GONE + recyclerView.visibility = View.VISIBLE + recyclerView.addItemDecoration(ListPaddingDecoration(this@NextServiceActivity)) + adapter = NextDayOrdersAdapter(this@NextServiceActivity, nextDayOrders) + val linearLayoutManager = LinearLayoutManager(this@NextServiceActivity) + recyclerView.adapter = adapter + recyclerView.layoutManager = linearLayoutManager + adapter!!.notifyDataSetChanged() + ordersViewModel.refreshNextDayOrders() + + } else { + tvEmpty.visibility = View.GONE + recyclerView.visibility = View.VISIBLE + adapter!!.setOrders(nextDayOrders) + adapter!!.notifyDataSetChanged() + } + + } else { + recyclerView.visibility = View.GONE + tvEmpty.visibility = View.VISIBLE + } + + if (swipeRefresh.isRefreshing) swipeRefresh.isRefreshing = false + } + } + + private fun viewBinding(){ + recyclerView = binding.recycler + swipeRefresh = binding.swipeRefresh + tvEmpty = binding.tvEmpty + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/OperatorsActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/OperatorsActivity.kt new file mode 100644 index 0000000..1993870 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/OperatorsActivity.kt @@ -0,0 +1,124 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.OperatorAdapter +import com.iesoluciones.siodrenax.databinding.ActivityOperatorsBinding +import com.iesoluciones.siodrenax.entities.Operator +import com.iesoluciones.siodrenax.utils.Constants.Companion.OPERATOR_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.OPERATOR_NAME +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration +import com.iesoluciones.siodrenax.viewmodels.ManagerViewModel + +class OperatorsActivity : ToolbarActivity(), OnRefreshListener, + OperatorAdapter.OnOperatorSelectedListener { + + private lateinit var binding: ActivityOperatorsBinding + private lateinit var managerViewModel: ManagerViewModel + var adapter: OperatorAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityOperatorsBinding.inflate(layoutInflater) + setContentView(binding.root) + + managerViewModel = ViewModelProvider(this)[ManagerViewModel::class.java] + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.t_OperatorsActivity) + toolbarToLoad(toolbar) + + attachToObservables() + binding.swipeRefresh.setOnRefreshListener(this) + managerViewModel.getOperators() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.workday, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.log_out -> showLogOutConfirmation() + } + return super.onOptionsItemSelected(item) + } + + private fun attachToObservables(){ + managerViewModel.onCallFailure.observe(this) { throwable: Throwable? -> + if (throwable != null) { + HelperUtil().parseError(this@OperatorsActivity, throwable) + managerViewModel.onCallFailure.value = null + } + } + + managerViewModel.isLoading.observe(this) { s: String? -> + if (s != null) { + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + binding.recycler.isEnabled = false + } else { + binding.frameLoading.visibility = View.GONE + binding.recycler.isEnabled = true + } + if (binding.swipeRefresh.isRefreshing) binding.swipeRefresh.isRefreshing = false + } + + managerViewModel.getOperatorList()!!.observe(this) { operators: List -> + + //Refresh adapter and recyclerView + if (adapter == null) { + adapter = OperatorAdapter(operators, this@OperatorsActivity) + binding.recycler.layoutManager = LinearLayoutManager(this@OperatorsActivity) + binding.recycler.addItemDecoration( + ListPaddingDecoration(this@OperatorsActivity) + ) + binding.recycler.adapter = adapter + } else { + adapter!!.operatorList = operators + adapter!!.notifyDataSetChanged() + } + } + + managerViewModel.onWorkdaySuccess.observe(this) { success: Boolean -> + if (success) { + startActivity(Intent(this@OperatorsActivity, LoginActivity::class.java)) + finish() + } + } + } + + override fun onRefresh() { + managerViewModel.getOperators() + } + + private fun showLogOutConfirmation() { + AlertDialog.Builder(this) + .setTitle(getString(R.string.ad_t_cerrar_sesion)) + .setMessage(getString(R.string.ad_m_cerrar_sesion)) + .setPositiveButton(getString(R.string.ad_aceptar)) { _, _ -> managerViewModel.endWorkload() } + .setNegativeButton(getString(R.string.ad_cancelar), null) + .setCancelable(false) + .show() + } + + override fun onOperatorClicked(operator: Operator) { + val intent = Intent(this@OperatorsActivity, OrdersManagerActivity::class.java) + intent.putExtra(OPERATOR_ID, operator.id) + intent.putExtra(OPERATOR_NAME,"${operator.name} ${operator.lastName} ${operator.mothersName}") + startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/OrderDetailActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrderDetailActivity.kt new file mode 100644 index 0000000..ed76f59 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrderDetailActivity.kt @@ -0,0 +1,1439 @@ +package com.iesoluciones.siodrenax.activities + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.provider.Settings +import android.telephony.PhoneNumberUtils +import android.text.Editable +import android.text.InputType +import android.text.TextWatcher +import android.util.Log +import android.view.View +import android.view.View.OnLongClickListener +import android.widget.ImageView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatEditText +import androidx.appcompat.widget.Toolbar +import androidx.core.app.ActivityCompat +import androidx.lifecycle.ViewModelProvider +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.adapters.OrdersAdapter.Companion.inFormatter +import com.iesoluciones.siodrenax.adapters.OrdersAdapter.Companion.outFormatter +import com.iesoluciones.siodrenax.databinding.ActivityOrderDetailBinding +import com.iesoluciones.siodrenax.entities.Evidence +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.entities.OrderProgress +import com.iesoluciones.siodrenax.models.OrderPdf +import com.iesoluciones.siodrenax.models.Reason +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import com.iesoluciones.siodrenax.repositories.SurveyRepository +import com.iesoluciones.siodrenax.utils.* +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LATITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LITERS +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LONGITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_CODE +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_NAME +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.REQUEST_PERMISSIONS_REQUEST_CODE +import com.iesoluciones.siodrenax.utils.Constants.Companion.SIGNATURE_CODE +import com.iesoluciones.siodrenax.utils.Constants.Companion.SURVEY_CODE +import com.iesoluciones.siodrenax.viewmodels.OrderProgressViewModel +import java.io.File +import java.text.NumberFormat +import java.text.ParseException +import java.util.* + + +class OrderDetailActivity : ToolbarActivity() { + + private lateinit var orderProgressViewModel: OrderProgressViewModel + private lateinit var binding: ActivityOrderDetailBinding + + private val ordersRepository = OrdersRepository() + private val surveyRepository = SurveyRepository() + + private lateinit var startImageViews: List + private lateinit var processImageViews: List + private lateinit var finalImageViews: List + private lateinit var actualImageView: ImageView + + private lateinit var order: Order + private lateinit var orderProgress: OrderProgress + private lateinit var reason: Array + + private var id: Int = 0 + private var elapsedTime: Long = 0 + private var orderId: Long = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityOrderDetailBinding.inflate(layoutInflater) + setContentView(binding.root) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + + startImageViews = listOf( + binding.ivEvidenceStart1, + binding.ivEvidenceStart2, + binding.ivEvidenceStart3 + ) + processImageViews = listOf( + binding.ivEvidenceProcess1, + binding.ivEvidenceProcess2, + binding.ivEvidenceProcess3 + ) + finalImageViews = listOf( + binding.ivEvidenceFinal1, + binding.ivEvidenceFinal2, + binding.ivEvidenceFinal3 + ) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.t_OrderDetailActivity) + toolbarToLoad(toolbar) + + editLiters = binding.editLiters + + orderProgressViewModel = ViewModelProvider(this)[OrderProgressViewModel::class.java] + + order = ordersRepository.getOrderById(intent.getLongExtra(ORDER_ID, 0L))!! + orderProgressViewModel.getEvidences(order.id) + orderId = intent.getLongExtra(ORDER_ID, 0L) + orderProgress = ordersRepository.getOrderProgressById(orderId)!! + + setUpViews() + attachObservers() + + if (orderProgress.endDate != null && !orderProgress.isOnSurvey) { + binding.tvTimer.text = orderProgress.elapsedTime + binding.tvFinishOrder.text = getString(R.string.firmar) + binding.tvFinishOrder.isEnabled = false + + if (orderProgress.signaturePath == null) { + val intent = Intent(this@OrderDetailActivity, SignatureActivity::class.java) + startActivityForResult(intent, SIGNATURE_CODE) + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.scale_out_airbnb + ) + } + + } else if (orderProgress.endDate != null && orderProgress.isOnSurvey) { + val intent = Intent(this@OrderDetailActivity, SurveyActivity::class.java) + intent.putExtra(ORDER_ID, orderProgress.id) + startActivityForResult(intent, SURVEY_CODE) + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.scale_out_airbnb + ) + } + + if (savedInstanceState != null) + orderProgressViewModel.getEvidences(order.id) + + binding.ivEvidenceStart1.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceStart2.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceStart3.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceProcess1.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceProcess2.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceProcess3.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceFinal1.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceFinal2.setOnClickListener { onClickEvidence(it as ImageView) } + binding.ivEvidenceFinal3.setOnClickListener { onClickEvidence(it as ImageView) } + binding.tvFinishOrder.setOnClickListener { onTvFinishOrderClick() } + binding.checkBoxNegativeService.setOnClickListener { onNegativeServiceClick() } + binding.showSketch.setOnClickListener { + if(order.sketchPath == null) + toast(getString(R.string.m_no_pdf_client), Toast.LENGTH_LONG) + else + goToActivity{ + putExtra(Constants.BRANCH_NAME, order.branchName) + putExtra(Constants.SKETCH_PATH, order.sketchPath) + } + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean("refresh", true) + } + + override fun onStop() { + super.onStop() + orderProgress.comments = binding.editComments.text.toString() + orderProgress.warranty = binding.checkBoxWarranty.isChecked + + if (binding.checkBoxNegativeService.isChecked) + orderProgress.negativeService = id + else + orderProgress.negativeService = 0 + + + if (editLiters!!.text.toString() != "" && editLiters!!.text.toString() != "0") { + try { + orderProgress.liters = + NumberFormat.getNumberInstance().parse(editLiters!!.text.toString()).toString() + .toInt() + } catch (e: ParseException) { + e.printStackTrace() + } + } + + ordersRepository.saveOrderProgress(orderProgress) + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { + when { + grantResults.isEmpty() -> { + Log.i(TAG, "User interaction was cancelled.") + } + grantResults[0] == PackageManager.PERMISSION_GRANTED -> { + onClickEvidence(actualImageView) + } + else -> { + AlertDialog.Builder(this) + .setTitle(getString(R.string.ad_t_permisos_necesarios)) + .setMessage(getString(R.string.ad_m_permisos_necesarios)) + .setCancelable(false) + .setPositiveButton( + getString(R.string.ad_aceptar) + ) { _, _ -> if (!checkPermissions()) requestPermissions() } + .show() + } + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (resultCode == RESULT_OK) { + orderProgress = ordersRepository.getOrderProgressById(orderId)!! + orderProgressViewModel.getEvidences(order.id) + if (requestCode == SIGNATURE_CODE) { + orderProgress.signaturePath = data!!.getStringExtra(EVIDENCE_ID) + orderProgress.signatureName = data.getStringExtra(EVIDENCE_NAME) + orderProgress.shouldBeSent = true + orderProgressBox.put(orderProgress) + preferencesHelper.orderInProgress = null + createPDF() + } + if (requestCode == SURVEY_CODE) { + val intent = Intent(this@OrderDetailActivity, SignatureActivity::class.java) + startActivityForResult(intent, SIGNATURE_CODE) + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.scale_out_airbnb + ) + } + } + if (resultCode == RESULT_CANCELED) { + if (requestCode == EVIDENCE_CODE) { + toastLong(getText(R.string.evidence_error)) + } + } + } + + private fun setUpViews() { + try { + val temp: Date = inFormatter.parse(order.dateServiceRequest)!! + binding.tvClientDenomination.text = order.clientDenomination + binding.tvOrderScheduledTime.text = getString( + R.string.scheduled_time, outFormatter.format( + temp + ) + ) + } catch (e: ParseException) { + e.printStackTrace() + binding.tvClientDenomination.text = order.clientContactName + } + + binding.tvOrderName.text = order.serviceName + binding.tvServiceStatus.text = order.serviceStatusName + + if (order.getClientContactCellphone.trim().isNotEmpty()) { + binding.tvContact.text = order.clientContactName + binding.tvContactPhone.text = getString( + R.string.contact_phone, PhoneNumberUtils.formatNumber( + order.getClientContactCellphone, + "MX" + ) + ) + binding.tvContactPhone.visibility = View.VISIBLE + } else { + binding.tvContact.text = order.clientContactName + binding.tvContactPhone.visibility = View.GONE + } + + binding.tvPayMethod.text = order.payMethodName + val drawable = StatusDrawable(order.serviceStatusColor1, order.serviceStatusColor2) + + binding.tvTitle.background = drawable + binding.tvOrderId.text = order.idRequest.toString() + binding.tvCost.text = getString(R.string.cost, order.cost.currencyFormat()) + binding.tvAddress.text = getString( + R.string.address, + order.streetAddress, + order.extNumberAddress, + order.neighborhoodNameAddress, + order.clientPostalCodeAddress + ) + + binding.editLiters.inputType = InputType.TYPE_CLASS_NUMBER + binding.editLiters.addTextChangedListener(CurrencyTextWatcher()) + binding.editLiters.setText(DEFAULT_LITERS) + + if (order.comments != null) { + binding.linearComments.visibility = View.VISIBLE + binding.tvComments.text = order.comments + } else { + binding.linearComments.visibility = View.GONE + } + + if (orderProgress.comments != null) + binding.editComments.setText(orderProgress.comments) + + + if (orderProgress.liters != 0) + binding.editLiters.setText(orderProgress.liters.toString()) + + if (orderProgress.warranty) + binding.checkBoxWarranty.isChecked = true + + if (orderProgress.negativeService > 0) { + binding.checkBoxNegativeService.isChecked = true + id = orderProgress.negativeService + binding.checkBoxNegativeService.text = getReason(id) + } else { + binding.checkBoxNegativeService.setText(R.string.negative) + } + + binding.linearContact.setOnLongClickListener(OnLongClickListener { + if (order.getClientContactCellphone.trim().isNotEmpty()) { + val callIntent = Intent(Intent.ACTION_CALL) + callIntent.data = Uri.parse("tel:" + order.getClientContactCellphone.trim()) + if (ActivityCompat.checkSelfPermission( + this@OrderDetailActivity, + Manifest.permission.CALL_PHONE + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + this@OrderDetailActivity, + arrayOf(Manifest.permission.CALL_PHONE), + 3 + ) + return@OnLongClickListener true + } + startActivity(callIntent) + } + + false + }) + } + private fun onClickEvidence(iv: ImageView) { + actualImageView = iv + + if (!checkPermissions()) + requestPermissions() + else { + val evidence = iv.tag as Evidence + if (evidence.path == null) { + //System.gc() + val intent = Intent(this@OrderDetailActivity, CameraActivity::class.java) + intent.putExtra(EVIDENCE_ID, evidence.id) + startActivityForResult(intent, EVIDENCE_CODE) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } else { + val intent = Intent(this@OrderDetailActivity, ConfirmationActivity::class.java) + intent.putExtra(EVIDENCE_ID, evidence.id) + startActivity(intent) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + } + } + + private fun attachObservers() { + if (orderProgress.elapsedTime == null) { + orderProgressViewModel.getTimer(orderProgress.startDate)!!.observe(this) { millis -> + if (millis != null) { + binding.tvTimer.text = HelperUtil().formatTime(millis) + elapsedTime = millis + } + } + } + + orderProgressViewModel.onEvidenceRefresh.observe(this) { evidences -> + if (evidences != null) { + try { + for (i in evidences.indices) { + val actualEvidence: Evidence = evidences[i] + when (actualEvidence.type) { + 1 -> { + if (actualEvidence.path != null) { + startImageViews[i].setImageBitmap( + HelperUtil().decodeFromFile( + actualEvidence.path!!, + 5 + ) + ) + } + startImageViews[i].tag = actualEvidence + } + 2 -> { + if (actualEvidence.path != null) { + processImageViews[i - 3].setImageBitmap( + HelperUtil().decodeFromFile( + actualEvidence.path!!, + 5 + ) + ) + } + processImageViews[i - 3].tag = actualEvidence + } + 3 -> { + if (actualEvidence.path != null) { + finalImageViews[i - 6].setImageBitmap( + HelperUtil().decodeFromFile( + actualEvidence.path!!, + 5 + ) + ) + } + finalImageViews[i - 6].tag = actualEvidence + } + } + } + } catch (e: Exception) { + Log.i( + TAG, + "Error al tratar de hacer map de evidencias, error: ${e.message}" + ) + } + } + } + + orderProgressViewModel.onSendSuccess.observe(this) { + startActivity(Intent(this@OrderDetailActivity, OrdersActivity::class.java)) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.slide_out_right_airbnb) + } + + orderProgressViewModel.onSendFailure.observe(this) { + startActivity(Intent(this@OrderDetailActivity, OrdersActivity::class.java)) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.slide_out_right_airbnb) + } + + orderProgressViewModel.isLoading.observe(this) { s -> + if (s != null) { + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + binding.tvFinishOrder.isEnabled = false + binding.ivEvidenceStart1.isEnabled = false + binding.ivEvidenceStart2.isEnabled = false + binding.ivEvidenceStart3.isEnabled = false + binding.ivEvidenceProcess1.isEnabled = false + binding.ivEvidenceProcess2.isEnabled = false + binding.ivEvidenceProcess3.isEnabled = false + binding.ivEvidenceFinal1.isEnabled = false + binding.ivEvidenceFinal2.isEnabled = false + binding.ivEvidenceFinal3.isEnabled = false + binding.editComments.isEnabled = false + editLiters!!.isEnabled = false + binding.checkBoxNegativeService.isEnabled = false + binding.checkBoxWarranty.isEnabled = false + } else { + binding.frameLoading.visibility = View.GONE + binding.ivEvidenceStart1.isEnabled = true + binding.ivEvidenceStart2.isEnabled = true + binding.ivEvidenceStart3.isEnabled = true + binding.ivEvidenceProcess1.isEnabled = true + binding.ivEvidenceProcess2.isEnabled = true + binding.ivEvidenceProcess3.isEnabled = true + binding.ivEvidenceFinal1.isEnabled = true + binding.ivEvidenceFinal2.isEnabled = true + binding.ivEvidenceFinal3.isEnabled = true + binding.editComments.isEnabled = true + editLiters!!.isEnabled = true + binding.checkBoxNegativeService.isEnabled = true + binding.checkBoxWarranty.isEnabled = true + } + } + orderProgressViewModel.isAllowedToFinishOrder.observe(this) { canFinish -> + if (canFinish != null) { + if (canFinish) { + if (isValidLitters()) { + AlertDialog.Builder(this@OrderDetailActivity) + .setTitle(getString(R.string.ad_t_servicio_exitoso)) + .setMessage(getString(R.string.ad_m_servicio_exitoso)) + .setPositiveButton( + getString(R.string.ad_si) + ) { _, _ -> + + //createZip(ordersRepository.getOrderById(orderProgress.id)!!) + + orderProgress.elapsedTime = HelperUtil().formatTime(elapsedTime) + if (editLiters!!.text.toString() == "") { + orderProgress.liters = 0 + } else { + try { + orderProgress.liters = + NumberFormat.getNumberInstance().parse( + editLiters!!.text.toString() + ).toString().toInt() + } catch (e: ParseException) { + e.printStackTrace() + } + } + orderProgress.comments = binding.editComments.text.toString() + + orderProgress.endLat = DEFAULT_LATITUDE.toString() + orderProgress.endLng = DEFAULT_LONGITUDE.toString() + orderProgress.endDate = inFormatter.format(Date()) + orderProgress.warranty = binding.checkBoxWarranty.isChecked + + if (binding.checkBoxNegativeService.isChecked) + orderProgress.negativeService = id + + if (order.SurveyRequired == 1) { + orderProgress.isOnSurvey = true + ordersRepository.saveOrderProgress(orderProgress) + + val intent = Intent( + this@OrderDetailActivity, + SurveyActivity::class.java + ) + intent.putExtra(ORDER_ID, orderProgress.id) + startActivityForResult( + intent, SURVEY_CODE + ) + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.scale_out_airbnb + ) + + } else { + ordersRepository.saveOrderProgress(orderProgress) + val intent = Intent( + this@OrderDetailActivity, + SignatureActivity::class.java + ) + startActivityForResult( + intent, SIGNATURE_CODE + ) + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.scale_out_airbnb + ) + } + } + .setNegativeButton(getString(R.string.ad_no), null) + .setCancelable(false) + .show() + + } else { + AlertDialog.Builder(this@OrderDetailActivity) + .setTitle(getString(R.string.ad_t_litraje_invalido)) + .setMessage(getString(R.string.ad_m_litraje_invalido)) + .setPositiveButton(getString(R.string.ad_aceptar), null) + .show() + } + + } else { + AlertDialog.Builder(this@OrderDetailActivity) + .setTitle(getString(R.string.ad_t_faltan_evidencias)) + .setMessage(getString(R.string.ad_m_faltan_evidencias_todas)) + .setPositiveButton(getString(R.string.ad_aceptar), null) + .show() + } + orderProgressViewModel.isAllowedToFinishOrder.value = null + } + } + } + + private fun isValidLitters(): Boolean { + return if (editLiters!!.text.toString() == "") { + true + } else { + try { + try { + orderProgress.liters = + NumberFormat.getNumberInstance().parse(editLiters!!.text.toString()) + .toString().toInt() + } catch (e: ParseException) { + e.printStackTrace() + } + true + } catch (e: java.lang.NumberFormatException) { + Log.e(TAG, "Error cachado en conversion litros: " + e.message) + false + } + } + } + + private fun checkPermissions(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + //Android is 11 (R) or above + val cameraPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + Environment.isExternalStorageManager() && cameraPermission == PackageManager.PERMISSION_GRANTED + } else { + //Below android 11 + PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) && PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } + + private fun requestPermissions() { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + val intent = Intent() + intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + val uri = Uri.fromParts("package", this.packageName, null) + intent.data = uri + storageActivityResultLauncher.launch(intent) + } catch (e: java.lang.Exception) { + val intent = Intent() + intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION + storageActivityResultLauncher.launch(intent) + } + } else { + //Below android 11 + ActivityCompat.requestPermissions( + this@OrderDetailActivity, + arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), + REQUEST_PERMISSIONS_REQUEST_CODE + ) + } + } + + private val storageActivityResultLauncher = registerForActivityResult(StartActivityForResult() + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + //Android is 11 (R) or above + if (Environment.isExternalStorageManager()) { + //Manage External Storage Permissions Granted + ActivityCompat.requestPermissions( + this@OrderDetailActivity, + arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), + REQUEST_PERMISSIONS_REQUEST_CODE + ) + + } else { + Toast.makeText( + this@OrderDetailActivity, + "Permiso denegado", + Toast.LENGTH_SHORT + ).show() + } + } else { + //Below android 11 + } + } + + private fun onTvFinishOrderClick() { + if (orderProgress.endDate == null) { + orderProgressViewModel.attemptFinishOrder(orderProgress.id.toString()) + } else { + val intent = Intent(this@OrderDetailActivity, SignatureActivity::class.java) + startActivityForResult(intent, SIGNATURE_CODE) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + } + + private fun onNegativeServiceClick() { + val radioButtonDialog: CustomDialogFragment = CustomDialogFragment(this).messageType( + CustomDialogFragment.MessageViewType.RADIOBUTTON + ) + if (binding.checkBoxNegativeService.isChecked) { + val reasons: List = surveyRepository.getNegativeServiceReason() + val descriptionList: ArrayList = ArrayList() + val idList: ArrayList = ArrayList() + for (i in reasons.indices) { + reasons[i].getTitle() + idList.add(reasons[i].getId()) + descriptionList.add(reasons[i].getTitle()) + } + reason = descriptionList.toTypedArray() + reason = arrayOfNulls(descriptionList.size) + descriptionList.toArray(reason) + radioButtonDialog.showRadioButtonMessage( + getString(R.string.service_title), + descriptionList.toArray(reason), + object : CustomDialogFragment.OnClickActionButton { + override fun onPositiveRadioButtonClicked(selection: Int) { + id = idList[selection].toInt() + binding.checkBoxNegativeService.text = getReason(id) + if (!binding.checkBoxNegativeService.isChecked) { + binding.checkBoxNegativeService.isChecked = true + } + } + + override fun onPositiveButtonClicked() {} + override fun onNegativeButtonClicked() { + if (binding.checkBoxNegativeService.isChecked) { + binding.checkBoxNegativeService.setText(R.string.negative) + binding.checkBoxNegativeService.isChecked = false + } + } + }).show() + } else { + binding.checkBoxNegativeService.setText(R.string.negative) + binding.checkBoxNegativeService.isChecked = false + } + } + + private fun getReason(q: Int): String { + return negativeServiceReasonBox.get(q.toLong()).getTitle() + } + + private fun mapOrderToPDF(): OrderPdf { + // MAPPING INFO + val order = ordersRepository.getOrderById(orderProgress.id)!! + val user = userBox.all[0] + + val auxDate = order.dateServiceRequest.split(' ').first().split('-') + val date = "${auxDate[2]}/${auxDate[1]}/${auxDate[0]}" + + val auxInt = if (order.intNumberAddress != null) " INT. ${order.intNumberAddress}" else "" + val auxCP = if (order.clientPostalCodeAddress != null) " C.P. ${order.clientPostalCodeAddress}" else "" + val address = "CALLE ${order.streetAddress} ${order.extNumberAddress} $auxInt, COL. ${order.neighborhoodNameAddress}$auxCP" + + var observations: String = orderProgress.comments ?: "" + var cost = "0.00" + var negativeCost = "0.00" + + if (orderProgress.negativeService > 0) { + val negativeServiceReason = negativeServiceReasonBox.get(orderProgress.negativeService.toLong()).getDescripcion() + observations = "Servicio negativo por: $negativeServiceReason.
$observations" + negativeCost = order.negativeCost + } else { + cost = order.cost + } + + val warrantyApplied = if (orderProgress.warranty) "Nota: Este servicio cuenta con 1 garantía con 3 meses de vigencia." else "" + + val startTime = orderProgress.startDate.split(' ').last() + val auxElapsedTime = orderProgress.elapsedTime!!.split(':') + val elapsedTime = "${auxElapsedTime[0]}hr ${auxElapsedTime[1]}m ${auxElapsedTime[2]}s" + val finishTime = orderProgress.endDate!!.split(' ').last() + + val advisor = "${user.username} ${user.lastName} ${user.mothersName}" + val auxiliar = if (order.assistant1Name != null) "${order.assistant1Name} ${order.assistant1LastName} ${order.assistant1MothersName}" else "" + + // GETTING SIGN BASE64 + val sign = HelperUtil().encodeBase64WithData(orderProgress.signaturePath!!) ?: "" + + // MAPPING AND GETTING EVIDENCE BASE64 + var evidencesMap: MutableMap> = HashMap() + evidencesMap["inicio"] = ArrayList() + evidencesMap["proceso"] = ArrayList() + evidencesMap["terminado"] = ArrayList() + + orderProgressViewModel.getEvidencesForPdf(order.id).forEach { + val evidenceBase64 = HelperUtil().encodeBase64WithData(it.path!!) ?: "" + + when (it.type) { + 1 -> evidencesMap["inicio"]!!.add(evidenceBase64) + 2 -> evidencesMap["proceso"]!!.add(evidenceBase64) + 3 -> evidencesMap["terminado"]!!.add(evidenceBase64) + } + } + + return OrderPdf( + order.idRequest, + order.clientDenomination, + date, + address, + order.clientCity, + order.getClientContactCellphone, + order.clientContactName, + order.serviceName, + observations, + warrantyApplied, + "$$cost", + "$$negativeCost", + startTime, + elapsedTime, + finishTime, + advisor, + order.operatorOfficeName, + sign, + auxiliar, + order.vehicleCodeName, + evidencesMap + ) + } + + private fun html(orderPdf: OrderPdf): String { + val srcLogo = "file:///android_asset/images/logo_drenax.png" + + return """ + + + + + DRENAX + + + + + +
+
+
+ +
+
+

Una Solución de Limpieza Integral

+

"Creamos ambientes limpios y seguros para el bienestar
de la comunidad"

+
+
+
DRENAX, S.A. DE C.V.
+ CALLE DIEZ #3482 + COL. SAN RAFAEL C.P. 80150 + CULIACÁN, SINALOA + R.F.C DRE0611035M4 + Tels. (667) 713 9250 y 60 + www.drenax.com.mx +
+
+
+ +
+
+ RECIBO DE SERVICIO +
+
+ ${orderPdf.requestId} +
+
+ +
+
+ +
+
+
Nombre
+
${orderPdf.client}
+
+
+
Fecha
+
${orderPdf.date}
+
+
+ +
+
+ Dirección +

${orderPdf.address}

+
+
+ +
+
+ Ciudad + ${orderPdf.city} +
+ +
+ Tel. + ${orderPdf.cellphone} +
+ +
+ Contacto + ${orderPdf.contactName} +
+
+
+
+ +
+
+

CON ESTA FECHA REALIZAMOS EL SERVICIO DE:   ${orderPdf.service}

+
+
+ +
+
+
+ Observaciones +

${orderPdf.observations}

+
+
${orderPdf.warrantyApplied}
+
+
+ +
+
+
+ EVIDENCIAS +
+
+
+ +
+
+
+
+ Inicio +
+ +
+
+ +
+
+ +
+
+ +
+ Proceso +
+ +
+
+ +
+
+ +
+
+ +
+ Terminado +
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ Costo de Servicio + ${orderPdf.cost} +
+
+ Costo Negativo + ${orderPdf.negativeCost} +
+
+ Hora de Inicio + ${orderPdf.startTime} hrs +
+
+ Duración + ${orderPdf.elapsedTime} +
+
+ Hora de Finalización + ${orderPdf.finishTime} hrs +
+
+
+ +
+
+ +
+
+
+ Asesor de Operaciones + ${orderPdf.advisor} +
+
+ Sucursal + ${orderPdf.branchOffice} +
+
+ +
+
+ Auxiliar de Operaciones + ${orderPdf.auxiliar} +
+
+ Vehículo + ${orderPdf.vehicle} +
+
+
+ +
+ Autorizó +
+ +
+
+ +
+
+ + + +""".trimIndent() + } + + private fun getPdfFilePath(): File? { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> { + getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + + } + Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT -> { + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + } + else -> { + File(Environment.getExternalStorageDirectory().toString() + "/Documents/") + } + } + } + + private fun createPDF() { + val orderPdf = mapOrderToPDF() + val htmlString = html(orderPdf) + //val pdfLocation = File(getPdfFilePath(), "${System.currentTimeMillis()}.pdf") + + val pdfLocation = File(filesDir, "${System.currentTimeMillis()}.pdf") + val htmlToPdfConvertor = CustomHtmlToPdfConvertor(this) + + htmlToPdfConvertor.convert( + pdfLocation = pdfLocation, + htmlString = htmlString, + onPdfGenerationFailed = { exception -> + exception.printStackTrace() + }, + onPdfGenerated = { pdfFile -> + order.pdfPath = pdfFile.path + orderBox.put(order) + HelperUtil().startSync() + startActivity(Intent(this@OrderDetailActivity, OrdersActivity::class.java)) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.slide_out_right_airbnb) + }) + } + + class CurrencyTextWatcher : TextWatcher { + private var current = "" + + @Synchronized + override fun afterTextChanged(s: Editable) { + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (s.toString() != current) { + editLiters!!.removeTextChangedListener(this) + val cleanString = s.toString().replace("[,]".toRegex(), "") + var parsed = 0 + try { + parsed = cleanString.toInt() + } catch (ignored: NumberFormatException) { + } + val formatted = NumberFormat.getNumberInstance().format(parsed.toLong()) + current = formatted + editLiters!!.setText(formatted) + editLiters!!.setSelection(formatted.length) + editLiters!!.addTextChangedListener(this) + } + } + } + + companion object { + var editLiters: AppCompatEditText? = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/OrderProgressActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrderProgressActivity.kt new file mode 100644 index 0000000..4e2524c --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrderProgressActivity.kt @@ -0,0 +1,373 @@ +package com.iesoluciones.siodrenax.activities + +import android.Manifest +import android.annotation.SuppressLint +import android.content.DialogInterface +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.* +import android.telephony.PhoneNumberUtils +import android.util.Log +import android.view.MenuItem +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.Toolbar +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.ViewModelProvider +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.OrdersAdapter.Companion.inFormatter +import com.iesoluciones.siodrenax.adapters.OrdersAdapter.Companion.outFormatter +import com.iesoluciones.siodrenax.databinding.ActivityOrderProgressBinding +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LATITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LONGITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_ID +import com.iesoluciones.siodrenax.viewmodels.OrderProgressViewModel +import java.text.ParseException +import java.util.* +import android.view.WindowManager +import android.widget.Toast +import com.iesoluciones.siodrenax.utils.* +import com.iesoluciones.siodrenax.utils.Constants.Companion.BRANCH_NAME +import com.iesoluciones.siodrenax.utils.Constants.Companion.SKETCH_PATH + +class OrderProgressActivity : ToolbarActivity() { + + private lateinit var binding: ActivityOrderProgressBinding + + //VIEWMODELS + private lateinit var orderProgressViewModel: OrderProgressViewModel + private val ordersRepository: OrdersRepository = OrdersRepository() + private var order: Order? = null + private var street = "" + private var extNumber = "" + private var neighborhoodName = "" + private var postalCode = "" + private val googleLocate = "geo:0,0?q=" + + //BINDINGS + private lateinit var tvClientDemonination: TextView + private lateinit var tvOrderName: TextView + private lateinit var tvPayMethod: TextView + private lateinit var tvContact: TextView + private lateinit var tvAddress: TextView + private lateinit var tvServiceStatus: TextView + private lateinit var tvTitle: TextView + private lateinit var tvCost: TextView + private lateinit var tvOrderId: TextView + private lateinit var tvOrderScheduledTime: TextView + private lateinit var tvContactPhone: TextView + private lateinit var tvLoading: TextView + private lateinit var tvComments: TextView + private lateinit var tvVehicle: TextView + private lateinit var startServiceButton: AppCompatButton + private lateinit var linearContact: LinearLayout + private lateinit var linearComments: LinearLayout + private lateinit var getRoute: LinearLayout + private lateinit var frameLoading: FrameLayout + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityOrderProgressBinding.inflate(layoutInflater) + setContentView(binding.root) + initializeBinding() + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = resources.getString(R.string.t_OrderProgressActivity) + toolbarToLoad(toolbar) + enableHomeDisplay(true) + toolbar.setNavigationOnClickListener { onBackPressed() } + + orderProgressViewModel = ViewModelProvider(this)[OrderProgressViewModel::class.java] + + attachObservers() + + order = ordersRepository.getOrderById(intent.getLongExtra(ORDER_ID, 0L)) + + if(order == null){ + toastLong(resources.getString(R.string.error_order_detail)) + return finish() + } + + val orderProgress = ordersRepository.getOrderProgressById(order!!.id) + + if (orderProgress != null) { + startServiceButton.isEnabled = false + startServiceButton.text = resources.getString(R.string.pendiente_envio) + } + + try { + val temp: Date = inFormatter.parse(order!!.dateServiceRequest)!! + tvClientDemonination.text = order!!.clientDenomination + tvOrderScheduledTime.text = getString( + R.string.scheduled_time, + outFormatter.format(temp) + ) + } catch (e: ParseException) { + e.printStackTrace() + tvClientDemonination.text = order!!.clientContactName + } + + tvOrderName.text = order!!.serviceName + tvVehicle.text = order!!.vehicleCodeName + + tvServiceStatus.text = + if (orderProgress != null) resources.getString(R.string.sincronizando) else order!!.serviceStatusName + + if (order!!.getClientContactCellphone.trim().isNotEmpty()) { + tvContact.text = order!!.clientContactName + tvContactPhone.text = getString( + R.string.contact_phone, PhoneNumberUtils.formatNumber( + order!!.getClientContactCellphone, + "MX" + ) + ) + tvContactPhone.visibility = View.VISIBLE + } else { + tvContact.text = order!!.clientContactName + tvContactPhone.visibility = View.GONE + } + + tvPayMethod.text = order!!.payMethodName + val drawable = StatusDrawable(order!!.serviceStatusColor1, order!!.serviceStatusColor2) + tvTitle.background = drawable + tvOrderId.text = "${order!!.idRequest}" + tvCost.text = getString(R.string.cost, order!!.cost.currencyFormat()) + tvAddress.text = getString( + R.string.address, + order!!.streetAddress, + order!!.extNumberAddress, + order!!.neighborhoodNameAddress, + order!!.clientPostalCodeAddress + ) + + if (order!!.comments != null) { + linearComments.visibility = View.VISIBLE + tvComments.text = order!!.comments + } else linearComments.visibility = View.GONE + + linearContact.setOnLongClickListener { + if (order!!.getClientContactCellphone.trim().isNotEmpty()) { + if (ActivityCompat.checkSelfPermission(this@OrderProgressActivity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((this@OrderProgressActivity as AppCompatActivity), arrayOf(Manifest.permission.CALL_PHONE), 3) + return@setOnLongClickListener true + }else{ + val callIntent = Intent(Intent.ACTION_CALL) + callIntent.data = Uri.parse("tel:" + order!!.getClientContactCellphone.trim()) // change the number + vibrate() + startActivity(callIntent) + } + } + false + } + + getRoute.setOnClickListener { + if (order!!.streetAddress != null) { + street = order!!.streetAddress!! + } + + if (order!!.extNumberAddress != null) { + val en: Int + try { + en = order!!.extNumberAddress!!.toInt() + if (en != 0) { + extNumber = en.toString() + } + } catch (e: NumberFormatException) { + Log.e(TAG, "Error cachado en conversion: " + e.message) + } + } + + if (order!!.neighborhoodNameAddress!= null) { + neighborhoodName = order!!.neighborhoodNameAddress!! + } + + if (order!!.clientPostalCodeAddress != null) { + val pc: Int + try { + pc = order!!.clientPostalCodeAddress!!.toInt() + if (pc != 0) { + postalCode = pc.toString() + } + } catch (e: NumberFormatException) { + Log.e(TAG, "Error cachado en conversion: " + e.message) + } + } + + if (street !== "" && extNumber !== "" && neighborhoodName !== "" && postalCode !== "") { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("$googleLocate$street+$extNumber+$neighborhoodName+$postalCode") + ) + startActivity(intent) + } else { + val addressDialog = + AlertDialog.Builder(this@OrderProgressActivity, R.style.MyAlertDialogStyle) + .setTitle(resources.getString(R.string.ad_t_direccion_servicio)) + .setMessage(resources.getString(R.string.ad_m_direccion_servicio)) + .setCancelable(false) + .setPositiveButton( + resources.getString(R.string.ir_al_mapa) + ) { _, _ -> + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("$googleLocate$street+$extNumber+$neighborhoodName+$postalCode") + ) + startActivity(intent) + } + .setNegativeButton( + resources.getString(R.string.cerrar) + ) { _, _ -> } + val ad = addressDialog.create() + ad.show() + val buttonPositive = ad.getButton(DialogInterface.BUTTON_POSITIVE) + buttonPositive.setTextColor(ContextCompat.getColor(this, R.color.primary)) + } + + } + startServiceButton.setOnClickListener { startService() } + + binding.showSketch.setOnClickListener { + if(order!!.sketchPath == null) + toast(getString(R.string.m_no_pdf_client), Toast.LENGTH_LONG) + else + goToActivity{ + putExtra(BRANCH_NAME, order!!.branchName) + putExtra(SKETCH_PATH, order!!.sketchPath) + } + } + } + + override fun onDestroy() { + super.onDestroy() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + + override fun onBackPressed() { + super.onBackPressed() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.slide_out_right_airbnb) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + finish() + overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.slide_out_right_airbnb + ) + } + } + return super.onOptionsItemSelected(item) + } + + private fun initializeBinding(){ + tvClientDemonination = binding.tvClientDenomination + tvOrderName = binding.tvOrderName + tvPayMethod = binding.tvPayMethod + tvContact = binding.tvContact + tvAddress = binding.tvAddress + tvServiceStatus = binding.tvServiceStatus + tvTitle = binding.tvTitle + tvCost = binding.tvCost + tvOrderId = binding.tvOrderId + tvOrderScheduledTime = binding.tvOrderScheduledTime + tvContactPhone = binding.tvContactPhone + tvLoading = binding.tvLoading + tvComments = binding.tvComments + tvVehicle = binding.tvVehicle + startServiceButton = binding.startServiceButton + linearContact = binding.linearContact + linearComments = binding.linearComments + getRoute = binding.getRoute + frameLoading = binding.frameLoading + } + + private fun startService() { + val alertDialogBuilder: AlertDialog.Builder = AlertDialog.Builder(this) + .setCancelable(false) + .setTitle(R.string.progress_start_dialog_title) + .setMessage(R.string.progress_start_dialog_message) + .setPositiveButton(R.string.accept) { _, _ -> + toastLong(getString(R.string.processing_request)) + orderProgressViewModel.startOrder( + order!!.id.toString(), + order!!.idRequest.toString(), + inFormatter.format(Date()), + DEFAULT_LATITUDE.toString(), + DEFAULT_LONGITUDE.toString() + ) + } + .setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.dismiss() } + + val alertDialog: AlertDialog = alertDialogBuilder.create() + + alertDialog.window!!.setFlags( + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + ) + alertDialog.window!!.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + alertDialog.show() + alertDialog.window!!.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) + } + + private fun attachObservers(){ + orderProgressViewModel.onSendSuccess.observe(this) { responseBody -> + if (responseBody != null) { + + if (order!!.SurveyRequired == 1) + ordersRepository.createSurveyAnswerHolders(order!!) + + val intent = Intent(this@OrderProgressActivity, OrderDetailActivity::class.java) + intent.putExtra(ORDER_ID, order!!.id) + intent.flags = (Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + orderProgressViewModel.onSendSuccess.value = null + startActivity(intent) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + } + + orderProgressViewModel.onSendFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this@OrderProgressActivity, throwable) + orderProgressViewModel.onSendFailure.value = null + } + } + + orderProgressViewModel.isLoading.observe(this) { s -> + if (s != null) { + frameLoading.visibility = View.VISIBLE + tvLoading.text = s + } else { + frameLoading.visibility = View.GONE + } + } + } + + @SuppressLint("MissingPermission") + private fun vibrate() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = this.getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + }else{ + (getSystemService(VIBRATOR_SERVICE) as Vibrator).vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/OrdersActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrdersActivity.kt new file mode 100644 index 0000000..36abbfe --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrdersActivity.kt @@ -0,0 +1,379 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.FrameLayout +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.adapters.OrdersAdapter +import com.iesoluciones.siodrenax.databinding.ActivityOrdersBinding +import com.iesoluciones.siodrenax.entities.CheckListQuestion +import com.iesoluciones.siodrenax.entities.Vehicle +import com.iesoluciones.siodrenax.interfaces.OnIncidentListener +import com.iesoluciones.siodrenax.interfaces.OnMileageListener +import com.iesoluciones.siodrenax.receivers.UpdateUIReceiver +import com.iesoluciones.siodrenax.receivers.UpdateUIReceiver.UiUpdateListener +import com.iesoluciones.siodrenax.utils.Constants +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LATITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LONGITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.MILEAGE +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration +import com.iesoluciones.siodrenax.viewmodels.IncidenceViewModel +import com.iesoluciones.siodrenax.viewmodels.OrdersViewModel +import com.iesoluciones.siodrenax.viewmodels.WorkdayStatusViewModel +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers +import java.io.* + +class OrdersActivity : ToolbarActivity(), OnMileageListener, OnIncidentListener, OnRefreshListener, UiUpdateListener { + + private lateinit var binding: ActivityOrdersBinding + private var localMileage: String? = null + + private lateinit var workdayStatusViewModel: WorkdayStatusViewModel + private lateinit var incidenceViewModel: IncidenceViewModel + private lateinit var ordersViewModel: OrdersViewModel + + private var adapter: OrdersAdapter? = null + + private lateinit var recyclerView: RecyclerView + private lateinit var swipeRefresh: SwipeRefreshLayout + private lateinit var frameHidden: FrameLayout + private lateinit var frameLoading: FrameLayout + private lateinit var tvLoading: TextView + private lateinit var tvEmpty: TextView + + private var changeVehicleAux = false + private var isWaitingForExitAux = false + + private lateinit var updateUIReceiver: UpdateUIReceiver + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Si se abre por medio de una notificación y hay una orden en progreso, entonces se cierra + if(intent.getBooleanExtra(Constants.EXTRA_NOTIFICATION_CLICKED, false) && preferencesHelper.orderInProgress != 0L) + return finish() + + if (savedInstanceState != null) { + localMileage = savedInstanceState.getString(MILEAGE) + } + + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + + binding = ActivityOrdersBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.t_OrdersActivity) + toolbarToLoad(toolbar) + + workdayStatusViewModel = ViewModelProvider(this)[WorkdayStatusViewModel::class.java] + incidenceViewModel = ViewModelProvider(this)[IncidenceViewModel::class.java] + ordersViewModel = ViewModelProvider(this)[OrdersViewModel::class.java] + + //Bindings + recyclerView = binding.recycler + swipeRefresh = binding.swipeRefresh + frameHidden = binding.frameHidden + frameLoading = binding.frameLoading + tvLoading = binding.tvLoading + tvEmpty = binding.tvEmpty + + attachToObservables() + + swipeRefresh.setOnRefreshListener { + onRefresh() + } + } + + + override fun onRefresh() { + ordersViewModel.isLoading.value = getString(R.string.loading) + ordersViewModel.refreshOrders(filesDir.absolutePath) + } + + override fun onMileageInput(mileage: String?) { + if (mileage != null) { + localMileage = mileage + HelperUtil().incidentDialogFragment(supportFragmentManager, this) + } + } + + override fun onNoIncident() { + logout() + } + + override fun onIncidentInput(incident: String?) { + if (incident != null) { + incidenceViewModel.sendVehicleIncidence(incident) + } + } + + override fun updateUI() { + ordersViewModel.updateOrdersFromDB() + } + + override fun onStart() { + super.onStart() + updateUIReceiver = UpdateUIReceiver() + updateUIReceiver.updateUIReceiver(this) + } + + override fun onResume() { + super.onResume() + updateUIReceiver.registerReceiver(this) + } + + + override fun onStop() { + super.onStop() + updateUIReceiver.unRegisterReceiver(this) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.orders, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.log_out -> workdayStatusViewModel.validateLogOut() + R.id.sync -> HelperUtil().startSync() + R.id.change_vehicle -> workdayStatusViewModel.validateChangeVehicle() + R.id.next_day_service -> { + val nextService = Intent(this, NextServiceActivity::class.java) + startActivity(nextService) + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToObservables() { + + workdayStatusViewModel.isLoading.observe(this) { s -> + if (s != null) { + // This is a hidden frame over the activity used to not permit click the screen + // this avoids to click the Drenax services when screen is loading + frameHidden.visibility = View.VISIBLE + frameLoading.visibility = View.VISIBLE + tvLoading.text = s + } else { + frameLoading.visibility = View.GONE + Handler(Looper.getMainLooper()).postDelayed( + { frameHidden.visibility = View.GONE }, + 500 + ) + } + } + + workdayStatusViewModel.endWorkdaySuccess.observe(this) { + workdayStatusViewModel.logout() + } + + workdayStatusViewModel.endWorkdayFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this@OrdersActivity, throwable) + workdayStatusViewModel.endWorkdayFailure.value = null + } + } + + ordersViewModel.getOrders().observe(this) { orders -> + if (orders.isNotEmpty()) { + if (adapter == null) { + + tvEmpty.visibility = View.GONE + recyclerView.visibility = View.VISIBLE + recyclerView.addItemDecoration(ListPaddingDecoration(this@OrdersActivity)) + adapter = OrdersAdapter(this@OrdersActivity, orders) + val linearLayoutManager = LinearLayoutManager(this@OrdersActivity) + recyclerView.adapter = adapter + recyclerView.layoutManager = linearLayoutManager + adapter!!.notifyDataSetChanged() + preferencesHelper.isEnableToSync = true + } else { + tvEmpty.visibility = View.GONE + recyclerView.visibility = View.VISIBLE + adapter!!.orders = orders + adapter!!.notifyDataSetChanged() + preferencesHelper.isEnableToSync = true + } + } else { + recyclerView.visibility = View.GONE + tvEmpty.visibility = View.VISIBLE + } + + if (swipeRefresh.isRefreshing) swipeRefresh.isRefreshing = false + } + + ordersViewModel.orderRefreshFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this@OrdersActivity, throwable) + ordersViewModel.orderRefreshFailure.postValue(null) + } + if (swipeRefresh.isRefreshing) swipeRefresh.isRefreshing = false + } + + workdayStatusViewModel.logOutSuccessObservable.observe(this) { logoutSuccess -> + if (logoutSuccess) { + startActivity(Intent(this@OrdersActivity, LoginActivity::class.java)) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + } + + workdayStatusViewModel.isAllowedToLogOut.observe(this) { isAllowed -> + if (isAllowed != null) { + if (isAllowed) { + showLogOutDialog(this@OrdersActivity) + } else { + showCantLogOutDialog(this@OrdersActivity) + } + workdayStatusViewModel.isAllowedToLogOut.value = null + } + } + + workdayStatusViewModel.isAllowedToChangeVehicle.observe(this) { isAllowed -> + if (isAllowed != null) { + if (isAllowed) { + showChangeVehicleConfirmation(this@OrdersActivity) + } else { + showCantChangeVehicleDialog(this@OrdersActivity) + } + workdayStatusViewModel.isAllowedToChangeVehicle.value = null + } + } + + workdayStatusViewModel.changeVehicleSuccess.observe(this) { responseBody -> + if (responseBody != null) { + val obCheckList: Observable> = api.getCheckList(1) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { checkListQuestions -> + checkListQuestionBox.put(checkListQuestions) + checkListQuestions + } + val obVehicle: Observable> = api.getVehicle() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { vehicle -> + vehicleBox.put(vehicle) + vehicle + } + + Observable.concat(obCheckList, obVehicle).subscribe(object : + ResourceObserver() { + override fun onNext(token: Any) {} + override fun onError(e: Throwable) {} + override fun onComplete() { + val intent = Intent( + this@OrdersActivity, + if (checkListQuestionBox.query().build().find().isNotEmpty()) + RevisionSurveyActivity::class.java else WorkdayActivity::class.java + ) + startActivity(intent) + finish() + } + }) + } + } + + incidenceViewModel.incidenceSuccess.observe(this) { response -> + if (response != null) { + logout() + } + } + } + + private fun logout() { + // These validates the kind of logout with auxiliary flags + if (isWaitingForExitAux) { + workdayStatusViewModel.endWorkday( + localMileage!!, + DEFAULT_LATITUDE.toString(), + DEFAULT_LONGITUDE.toString() + ) + } else if (changeVehicleAux) { + workdayStatusViewModel.changeVehicle( + localMileage!!, + DEFAULT_LATITUDE.toString(), + DEFAULT_LONGITUDE.toString() + ) + } + + workdayStatusViewModel.isLoading.value = getString(R.string.loading_wait_a_moment) + } + + private fun showLogOutDialog(context: Context?) { + AlertDialog.Builder(context!!) + .setTitle(R.string.logout_dialog_title) + .setMessage(R.string.logout_dialog_message) + .setPositiveButton(R.string.accept) { _, _ -> + isWaitingForExitAux = true + HelperUtil().mileageDialogFragment(supportFragmentManager, this) + } + .setNegativeButton(R.string.cancel) { dialogInterface, _ -> dialogInterface.dismiss() } + .setCancelable(false) + .show() + } + + private fun showCantLogOutDialog(context: Context?) { + AlertDialog.Builder(context!!) + .setTitle(R.string.cant_logout_dialog_title) + .setMessage(R.string.cant_logout_dialog_message) + .setPositiveButton(R.string.accept, null) + .setCancelable(false) + .show() + } + + private fun showChangeVehicleConfirmation(context: Context?) { + AlertDialog.Builder(context!!) + .setTitle(getString(R.string.ad_t_cambiar_vehiculo)) + .setMessage(getString(R.string.ad_m_cambiar_vehiculo)) + .setPositiveButton(getString(R.string.ad_aceptar)) { _, _ -> + changeVehicleAux = true + HelperUtil().mileageDialogFragment(supportFragmentManager, this@OrdersActivity) + } + .setNegativeButton(R.string.ad_cancelar) { dialogInterface, _ -> dialogInterface.dismiss() } + .setCancelable(false) + .show() + } + + private fun showCantChangeVehicleDialog(context: Context?) { + AlertDialog.Builder(context!!) + .setTitle(R.string.cant_logout_dialog_title) + .setMessage(R.string.cant_change_vehicle_dialog_message) + .setPositiveButton(R.string.accept) { dialogInterface, _ -> dialogInterface.dismiss() } + .setCancelable(false) + .show() + } + + /*private fun showAutoCloseDialog(context: Context?) { + AlertDialog.Builder(context!!) + .setMessage(R.string.auto_close) + .setPositiveButton(R.string.accept) { _, _ -> + workdayStatusViewModel.logout() + } + .setCancelable(false) + .show() + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/OrdersManagerActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrdersManagerActivity.kt new file mode 100644 index 0000000..ea9a257 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/OrdersManagerActivity.kt @@ -0,0 +1,104 @@ +package com.iesoluciones.siodrenax.activities + +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.adapters.OrdersManagerAdapter +import com.iesoluciones.siodrenax.databinding.ActivityOrdersManagerBinding +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.utils.Constants.Companion.OPERATOR_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.OPERATOR_NAME +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration +import com.iesoluciones.siodrenax.viewmodels.ManagerViewModel + +class OrdersManagerActivity : ToolbarActivity(), OnRefreshListener { + + private lateinit var binding: ActivityOrdersManagerBinding + private lateinit var managerViewModel: ManagerViewModel + var adapter: OrdersManagerAdapter? = null + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityOrdersManagerBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = intent.getStringExtra(OPERATOR_NAME) + toolbarToLoad(toolbar) + + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + + managerViewModel = ViewModelProvider(this)[ManagerViewModel::class.java] + binding.swipeRefresh.setOnRefreshListener(this) + attachObservers() + + managerViewModel.getOperatorOrders( + intent.getLongExtra(OPERATOR_ID, 0L).toString() + "" + ) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + finish() + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachObservers(){ + managerViewModel.onOrdersRetrieveSuccess.observe(this + ) { orders: List -> + if (orders.isNotEmpty()) { + if (adapter == null) { + binding.tvEmpty.visibility = View.GONE + binding.recycler.visibility = View.VISIBLE + binding.recycler.addItemDecoration(ListPaddingDecoration(this@OrdersManagerActivity)) + adapter = OrdersManagerAdapter(this@OrdersManagerActivity, orders) + val linearLayoutManager = LinearLayoutManager(this@OrdersManagerActivity) + binding.recycler.adapter = adapter + binding.recycler.layoutManager = linearLayoutManager + adapter!!.notifyDataSetChanged() + } else { + binding.tvEmpty.visibility = View.GONE + binding.recycler.visibility = View.VISIBLE + adapter!!.orders = orders + adapter!!.notifyDataSetChanged() + } + } else { + binding.recycler.visibility = View.GONE + binding.tvEmpty.visibility = View.VISIBLE + } + if (binding.swipeRefresh.isRefreshing) binding.swipeRefresh.isRefreshing = false + } + + managerViewModel.onCallFailure.observe(this) { throwable: Throwable? -> + if (throwable != null) HelperUtil().parseError(this@OrdersManagerActivity, throwable) + if (binding.swipeRefresh.isRefreshing) binding.swipeRefresh.isRefreshing = false + } + + managerViewModel.isLoading.observe(this + ) { s: String? -> + if (s != null) { + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + } else { + binding.frameLoading.visibility = View.GONE + } + } + } + + override fun onRefresh() { + managerViewModel.getOperatorOrders( + intent.getLongExtra(OPERATOR_ID, 0L).toString() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/PdfViewerActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/PdfViewerActivity.kt new file mode 100644 index 0000000..b5e3ef2 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/PdfViewerActivity.kt @@ -0,0 +1,110 @@ +package com.iesoluciones.siodrenax.activities + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.pdf.PdfRenderer +import android.os.Bundle +import android.os.ParcelFileDescriptor +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.PdfViewerAdapter +import com.iesoluciones.siodrenax.databinding.ActivityPdfViewerBinding +import com.iesoluciones.siodrenax.utils.Constants.Companion.BRANCH_NAME +import com.iesoluciones.siodrenax.utils.Constants.Companion.SKETCH_PATH +import java.io.File +import java.io.IOException + +class PdfViewerActivity : ToolbarActivity() { + + private lateinit var binding: ActivityPdfViewerBinding + private lateinit var recyclerView: RecyclerView + private var adapter: PdfViewerAdapter? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityPdfViewerBinding.inflate(layoutInflater) + setContentView(binding.root) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = intent.getStringExtra(BRANCH_NAME) + toolbarToLoad(toolbar) + enableHomeDisplay(true) + recyclerView = binding.recycler + + + val file = File(intent.getStringExtra(SKETCH_PATH)!!) + + try { + binding.tvEmpty.visibility = View.GONE; + openPDF(file) + } catch (ioe: IOException) { + binding.tvEmpty.visibility = View.VISIBLE; + ioe.printStackTrace() + } + + toolbar.setNavigationOnClickListener { + finish() + } + } + + @Throws(IOException::class) + fun openPDF(file: File?) { + var fileDescriptor: ParcelFileDescriptor? = null + fileDescriptor = ParcelFileDescriptor.open( + file, ParcelFileDescriptor.MODE_READ_ONLY + ) + + var pdfRenderer: PdfRenderer? = null + pdfRenderer = PdfRenderer(fileDescriptor) + val pageCount: Int = pdfRenderer.pageCount + + val pages : MutableList = ArrayList() + + for (i in 0 until pageCount){ + val rendererPage: PdfRenderer.Page = pdfRenderer.openPage(i) + val rendererPageWidth: Int = rendererPage.width + val rendererPageHeight: Int = rendererPage.height + val bitmap: Bitmap = Bitmap.createBitmap( + rendererPageWidth, + rendererPageHeight, + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + canvas.drawColor(Color.WHITE) + canvas.drawBitmap(bitmap, 0f, 0f, null) + + rendererPage.render( + bitmap, null, null, + PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY + ) + + pages.add(bitmap) + rendererPage.close() + } + + pdfRenderer.close() + fileDescriptor.close() + + if(adapter == null){ + recyclerView.visibility = View.VISIBLE + adapter = PdfViewerAdapter(this@PdfViewerActivity, pages) + val linearLayoutManager = LinearLayoutManager(this@PdfViewerActivity) + recyclerView.adapter = adapter + recyclerView.layoutManager = linearLayoutManager + adapter!!.notifyDataSetChanged() + }else{ + recyclerView.visibility = View.VISIBLE + adapter!!.pages = pages + adapter!!.notifyDataSetChanged() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/RevisionSurveyActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/RevisionSurveyActivity.kt new file mode 100644 index 0000000..43309cf --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/RevisionSurveyActivity.kt @@ -0,0 +1,155 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.RevisionSurveyAdapter +import com.iesoluciones.siodrenax.databinding.ActivityRevisionSurveyBinding +import com.iesoluciones.siodrenax.interfaces.OnIncidentShowListener +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.CheckListRepository +import com.iesoluciones.siodrenax.utils.Constants +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.ListPaddingDecoration +import com.iesoluciones.siodrenax.utils.toast +import com.iesoluciones.siodrenax.vehicleBox +import com.iesoluciones.siodrenax.viewmodels.IncidenceViewModel + +class RevisionSurveyActivity : ToolbarActivity(), OnIncidentShowListener { + + private lateinit var binding: ActivityRevisionSurveyBinding + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: RevisionSurveyAdapter + private lateinit var incidenceViewModel: IncidenceViewModel + private var incidenceId: Int? = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityRevisionSurveyBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.revision) + toolbarToLoad(toolbar) + + preferencesHelper.checkListProgress = Constants.REVISION + binding.btnSiguiente.text = getString(R.string.siguiente) + + incidenceViewModel = ViewModelProvider(this)[IncidenceViewModel::class.java] + attachObservers() + + val checkListQuestion = CheckListRepository().getSurveyCheckList(Constants.REVISION) + + val vehicle: ArrayList = ArrayList() + val vehicleIds: ArrayList = ArrayList() + vehicle.add(getString(R.string.seleccionaVehiculo)) + vehicleIds.add(0) + + for (v in vehicleBox.all){ + vehicle.add(v.nombre) + vehicleIds.add(v.id) + } + + recyclerView = binding.recycler + recyclerView.visibility = View.VISIBLE + recyclerView.addItemDecoration(ListPaddingDecoration(this@RevisionSurveyActivity)) + + adapter = RevisionSurveyAdapter( + binding.btnSiguiente, + checkListQuestion, + vehicle, + vehicleIds + ) + + recyclerView.adapter = adapter + recyclerView.layoutManager = LinearLayoutManager(this) + + binding.btnSiguiente.setOnClickListener { + recyclerView.clearFocus() + onClickBtnSiguiente() + } + } + + private fun onClickBtnSiguiente() { + incidenceViewModel.getVehicleIncidence(RevisionSurveyAdapter.vehicleSelectedId) + } + + private fun attachObservers() { + incidenceViewModel.isLoading.observe(this) { s -> + if (s != null) { + binding.btnSiguiente.isEnabled = false + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + } else { + binding.btnSiguiente.isEnabled = true + binding.frameLoading.visibility = View.GONE + } + } + + incidenceViewModel.incidenceSuccess.observe(this) { response -> + if (response != null) { + toast(getString(R.string.toast_incidence_solved)) + alertDialog() + } + } + + incidenceViewModel.incidenceFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this, throwable) + incidenceViewModel.incidenceFailure.postValue(null) + } + } + + incidenceViewModel.getIncidenceSuccess.observe(this) { vehicleIncidenceResponse -> + if (vehicleIncidenceResponse != null && vehicleIncidenceResponse.id > 0) { + incidenceId = vehicleIncidenceResponse.id + val incidence: String = vehicleIncidenceResponse.description + HelperUtil().incidentShowDialogFragment(supportFragmentManager, this, incidence) + } else { + alertDialog() + } + } + + incidenceViewModel.getIncidenceFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this, throwable) + incidenceViewModel.getIncidenceFailure.postValue(null) + } + } + } + + private fun alertDialog() { + AlertDialog.Builder(this@RevisionSurveyActivity) + .setTitle(R.string.titleDialogEncuestaOperador) + .setMessage(R.string.messageDialogEncuestaOperador) + .setPositiveButton(R.string.accept) { _, _ -> + preferencesHelper.checkListProgress = Constants.MATERIAL + val intent = Intent(this@RevisionSurveyActivity, MaterialSurveyActivity::class.java) + startActivity(intent) + finish() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + override fun onIncidenceSolved() { + incidenceViewModel.resolveVehicleIncidence(incidenceId.toString()) + } + + override fun onIncidenceNotSolved() { + alertDialog() + } + + override fun onStop() { + super.onStop() + adapter.checkListQuestionBoxObserve!!.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/SignatureActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/SignatureActivity.kt new file mode 100644 index 0000000..d54528d --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/SignatureActivity.kt @@ -0,0 +1,122 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.os.Bundle +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatButton +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.lifecycle.ViewModelProvider +import com.github.gcacace.signaturepad.views.SignaturePad +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ActivitySignatureBinding +import com.iesoluciones.siodrenax.models.EvidenceSignatureLocal +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANIMATE_FADE_IN_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANIMATE_FADE_IN_DURATION +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANIMATE_FADE_OUT_ALPHA +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANIMATE_FADE_OUT_DURATION +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_NAME +import com.iesoluciones.siodrenax.utils.snackBar +import com.iesoluciones.siodrenax.utils.toast +import com.iesoluciones.siodrenax.viewmodels.EvidenceViewModel + +class SignatureActivity : AppCompatActivity(), SignaturePad.OnSignedListener { + + private var isAtLeastSigned = false + + private lateinit var binding: ActivitySignatureBinding + private lateinit var evidenceViewModel: EvidenceViewModel + + private lateinit var coordinatorSignature: CoordinatorLayout + private lateinit var signaturePad: SignaturePad + private lateinit var tvTitle: TextView + private lateinit var buttonClear: AppCompatButton + private lateinit var buttonPrint: AppCompatButton + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + binding = ActivitySignatureBinding.inflate(layoutInflater) + setContentView(binding.root) + viewBinding() + + signaturePad.setOnSignedListener(this) + evidenceViewModel = ViewModelProvider(this)[EvidenceViewModel::class.java] + attachObservers() + + buttonClear.setOnClickListener { onClickIvClear() } + buttonPrint.setOnClickListener { onClickFabCheck() } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.clear() + } + + override fun onStartSigning() { + animateFadeOut() + } + + override fun onSigned() { + isAtLeastSigned = true + animateFadeIn() + } + + override fun onClear() {} + + override fun onBackPressed() {} + + private fun animateFadeOut() { + tvTitle.animate().alpha(ANIMATE_FADE_OUT_ALPHA) + .setDuration(ANIMATE_FADE_OUT_DURATION).start() + buttonClear.animate().alpha(ANIMATE_FADE_OUT_ALPHA) + .setDuration(ANIMATE_FADE_OUT_DURATION).start() + buttonPrint.animate().alpha(ANIMATE_FADE_OUT_ALPHA) + .setDuration(ANIMATE_FADE_OUT_DURATION).start() + } + + private fun animateFadeIn() { + tvTitle.animate().alpha(ANIMATE_FADE_IN_ALPHA) + .setDuration(ANIMATE_FADE_IN_DURATION).start() + buttonPrint.animate().alpha(ANIMATE_FADE_IN_ALPHA) + .setDuration(ANIMATE_FADE_IN_DURATION).start() + buttonClear.animate().alpha(ANIMATE_FADE_IN_ALPHA) + .setDuration(ANIMATE_FADE_IN_DURATION).start() + } + + private fun onClickIvClear() { + signaturePad.clear() + isAtLeastSigned = false + } + + private fun onClickFabCheck() { + if (isAtLeastSigned) + evidenceViewModel.saveImage(filesDir, signaturePad.signatureBitmap) + else + snackBar(getString(R.string.s_dibuje_firma)) + } + + private fun attachObservers() { + evidenceViewModel.onImageSaved.observe(this) { evidenceSignatureLocal: EvidenceSignatureLocal -> + if (evidenceSignatureLocal.path != null && evidenceSignatureLocal.name != null) { + val intent = Intent() + intent.putExtra(EVIDENCE_ID, evidenceSignatureLocal.path) + intent.putExtra(EVIDENCE_NAME, evidenceSignatureLocal.name) + setResult(RESULT_OK, intent) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } else { + toast(getString(R.string.toast_ocurrio_problema)) + } + } + } + + private fun viewBinding() { + coordinatorSignature = binding.coordinatorSignature + signaturePad = binding.signaturePad + tvTitle = binding.tvTitle + buttonClear = binding.buttonClear + buttonPrint = binding.buttonPrint + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/SplashActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/SplashActivity.kt new file mode 100644 index 0000000..bdd3bc3 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/SplashActivity.kt @@ -0,0 +1,104 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.os.Bundle +import android.view.View.SYSTEM_UI_FLAG_FULLSCREEN +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.databinding.ActivitySplashBinding +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.CheckListRepository +import com.iesoluciones.siodrenax.utils.Constants +import com.iesoluciones.siodrenax.utils.Constants.Companion.LOGGED_IN +import com.iesoluciones.siodrenax.utils.Constants.Companion.LOGGED_IN_MANAGER +import com.iesoluciones.siodrenax.utils.Constants.Companion.NO_SESSION +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_IN_PROGRESS +import com.iesoluciones.siodrenax.utils.Constants.Companion.WORKDAY_STARTED +import com.iesoluciones.siodrenax.utils.Constants.Companion.WORKDAY_STARTED_MANAGER +import com.iesoluciones.siodrenax.utils.goToActivity +import com.iesoluciones.siodrenax.viewmodels.SplashViewModel + +class SplashActivity : AppCompatActivity() { + + private lateinit var binding: ActivitySplashBinding + private var splashViewModel: SplashViewModel? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySplashBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = (SYSTEM_UI_FLAG_FULLSCREEN) + + splashViewModel = ViewModelProvider(this)[SplashViewModel::class.java] + attachObservers() + + splashViewModel!!.getStartSplashObservable() + } + + private fun attachObservers() { + splashViewModel!!.sessionStatusObservable.observe(this) { status -> + processStatus(status!!) + } + } + + private fun processStatus(status: Int) { + when (status) { + LOGGED_IN -> { + + var progressActivity: Class<*> = WorkdayActivity::class.java + if (CheckListRepository().isCheckListDone()) { + when (App.prefs!!.checkListProgress) { + Constants.REVISION -> progressActivity = RevisionSurveyActivity::class.java + Constants.MATERIAL -> progressActivity = MaterialSurveyActivity::class.java + Constants.HERRAMIENTA -> progressActivity = + HerramientaSurveyActivity::class.java + Constants.WORKDAY -> progressActivity = WorkdayActivity::class.java + else -> { + } + } + } + val intent = Intent(this@SplashActivity, progressActivity) + startActivity(intent) + finish() + } + LOGGED_IN_MANAGER -> { + goToActivity { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + } + finish() + } + WORKDAY_STARTED -> { + goToActivity { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + } + finish() + } + WORKDAY_STARTED_MANAGER -> { + goToActivity { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + } + finish() + } + NO_SESSION -> { + goToActivity { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + } + finish() + } + ORDER_IN_PROGRESS -> { + goToActivity { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + putExtra(ORDER_ID, preferencesHelper.orderInProgress) + } + finish() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/SurveyActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/SurveyActivity.kt new file mode 100644 index 0000000..d4722db --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/SurveyActivity.kt @@ -0,0 +1,162 @@ +package com.iesoluciones.siodrenax.activities + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.ScrollView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.Toolbar +import androidx.core.widget.NestedScrollView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.MultipleViewHolderAdapter +import com.iesoluciones.siodrenax.databinding.ActivitySurveyBinding +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.entities.OrderProgress +import com.iesoluciones.siodrenax.models.Question +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import com.iesoluciones.siodrenax.repositories.SurveyRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.DRIVE_READER +import com.iesoluciones.siodrenax.utils.Constants.Companion.IDSERVICETYPEDOMESTIC +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.PDF_URL +import com.iesoluciones.siodrenax.utils.toast + +class SurveyActivity : ToolbarActivity() { + + private var orderId: Long = 0 + private var isDomestic = false + private lateinit var skipItem: MenuItem + private lateinit var acceptItem: MenuItem + private lateinit var order: Order + private lateinit var orderProgress: OrderProgress + + private lateinit var binding: ActivitySurveyBinding + private lateinit var ordersRepository: OrdersRepository + private lateinit var surveyRepository: SurveyRepository + + private lateinit var recycler: RecyclerView + private lateinit var svWebView: ScrollView + private lateinit var nsvSurvey: NestedScrollView + private lateinit var webview: WebView + private lateinit var btnPrivacyAdvice: AppCompatButton + private lateinit var btnSaveSurvey: AppCompatButton + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + binding = ActivitySurveyBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.t_SurveyActivity) + toolbarToLoad(toolbar) + + viewBinding() + + ordersRepository = OrdersRepository() + surveyRepository = SurveyRepository() + + orderId = intent.getLongExtra(ORDER_ID, 0L) + order = ordersRepository.getOrderById(orderId)!! + orderProgress = ordersRepository.getOrderProgressById(orderId)!! + isDomestic = order.idServiceType == IDSERVICETYPEDOMESTIC + + val surveyQuestions: List = surveyRepository.getSurveyQuestions(isDomestic) + + val adapter = MultipleViewHolderAdapter(surveyQuestions, order) + + recycler.layoutManager = LinearLayoutManager(this) + recycler.setHasFixedSize(true) + recycler.adapter = adapter + + btnPrivacyAdvice.setOnClickListener { onPrivacyAdviceClick() } + btnSaveSurvey.setOnClickListener { onSurveyButtonClick() } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.survey, menu) + skipItem = menu.findItem(R.id.skip) + acceptItem = menu.findItem(R.id.accept) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.skip -> AlertDialog.Builder(this) + .setTitle(getString(R.string.ad_t_omitir_encuesta)) + .setMessage(getString(R.string.ad_m_omitir_encuesta)) + .setPositiveButton(R.string.accept) { _, _ -> + orderProgress.isOnSurvey = false + ordersRepository.saveOrderProgress(orderProgress) + surveyRepository.deleteAllSavedAnswers(order) + setResult(RESULT_OK) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } + .setNegativeButton(R.string.cancel, null) + .show() + R.id.accept -> { + svWebView.visibility = View.GONE + nsvSurvey.visibility = View.VISIBLE + acceptItem.isVisible = false + skipItem.isVisible = true + } + } + return super.onOptionsItemSelected(item) + } + + override fun onBackPressed() {} + + @SuppressLint("SetJavaScriptEnabled") + private fun onPrivacyAdviceClick() { + skipItem.isVisible = false + acceptItem.isVisible = true + webview.settings.javaScriptEnabled = true + webview.loadUrl(DRIVE_READER + PDF_URL) + webview.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + view.loadUrl(url) + return true + } + } + nsvSurvey.visibility = View.GONE + svWebView.visibility = View.VISIBLE + } + + private fun onSurveyButtonClick() { + recycler.requestFocus() + recycler.requestFocusFromTouch() + btnSaveSurvey.isEnabled = false + + if (surveyRepository.validateAnswers(order)) { + btnSaveSurvey.isEnabled = true + orderProgress.isOnSurvey = false + orderProgress.shouldSendSurvey = true + ordersRepository.saveOrderProgress(orderProgress) + setResult(RESULT_OK) + finish() + overridePendingTransition(R.anim.slide_in_rigth_airbnb, R.anim.scale_out_airbnb) + } else { + toast(R.string.toast_faltan_campos) + btnSaveSurvey.isEnabled = true + } + } + + private fun viewBinding() { + recycler = binding.recycler + svWebView = binding.svWebView + nsvSurvey = binding.nsvSurvey + webview = binding.webview + btnPrivacyAdvice = binding.btnPrivacyAdvice + btnSaveSurvey = binding.btnSaveSurvey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/WorkdayActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/WorkdayActivity.kt new file mode 100644 index 0000000..55f0ade --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/WorkdayActivity.kt @@ -0,0 +1,137 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.telephony.PhoneNumberUtils +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ActivityWorkdayBinding +import com.iesoluciones.siodrenax.interfaces.OnMileageListener +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.WorkdayRepository +import com.iesoluciones.siodrenax.utils.CircularProgressButton +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LATITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LONGITUDE +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.viewmodels.WorkdayStatusViewModel +import java.text.SimpleDateFormat +import java.util.* + +class WorkdayActivity : ToolbarActivity(), CircularProgressButton.ProgressListener, OnMileageListener { + + private lateinit var binding: ActivityWorkdayBinding + private lateinit var workdayStatusViewModel: WorkdayStatusViewModel + private lateinit var tokenFirebase: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityWorkdayBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.t_WorkdayActivity) + toolbarToLoad(toolbar) + + val user = WorkdayRepository().getUser() + binding.tvUsername.text = getString(R.string.username, user.username, user.lastName, user.mothersName) + binding.tvInitials.text = user.username.substring(0, 1) + binding.tvPhone.text = PhoneNumberUtils.formatNumber(user.phone, "MX") + val inFormatter = SimpleDateFormat("dd 'de' MMM 'de' yyyy", Locale.getDefault()) + binding.tvDate.text = inFormatter.format(Date()) + + binding.iniciarJornadaButton.progressListener = this + workdayStatusViewModel = ViewModelProvider(this)[WorkdayStatusViewModel::class.java] + attachToObservables() + tokenFirebase = preferencesHelper.tokenFirebase!! + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.workday, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.log_out -> workdayStatusViewModel.logout() + } + return super.onOptionsItemSelected(item) + } + + override fun onProgressStart() {} + + override fun onProgressFinish() { + HelperUtil().mileageDialogFragment(supportFragmentManager, this) + } + + override fun onMileageInput(mileage: String?) { + if (mileage != null) { + workdayStatusViewModel.isLoading.value = getString(R.string.loading_wait_a_moment) + workdayStatusViewModel.startWorkday( + tokenFirebase, + mileage, + DEFAULT_LATITUDE.toString(), + DEFAULT_LONGITUDE.toString(), + filesDir.absolutePath + ) + } + } + + private fun attachToObservables() { + workdayStatusViewModel.isLoading.observe(this) { s -> + if (s != null) { + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + binding.iniciarJornadaButton.deactivate() + } else { + binding.frameLoading.visibility = View.GONE + binding.iniciarJornadaButton.activate() + } + } + + workdayStatusViewModel.logOutSuccessObservable.observe(this) { logoutSuccess -> + if (logoutSuccess != null) if (logoutSuccess) { + startActivity(Intent(this@WorkdayActivity, LoginActivity::class.java)) + finish() + } + } + + workdayStatusViewModel.startWorkdaySuccess.observe(this) { + startActivity(Intent(this@WorkdayActivity, OrdersActivity::class.java)) + finish() + } + + workdayStatusViewModel.startWorkdayFailure.observe(this) { throwable -> + if (throwable != null) { + HelperUtil().parseError(this, throwable) + workdayStatusViewModel.startWorkdayFailure.value = null + } + } + + workdayStatusViewModel.endWorkdaySuccess.observe(this) { + startActivity(Intent(this@WorkdayActivity, LoginActivity::class.java)) + finish() + } + + workdayStatusViewModel.endWorkdayFailure.observe(this) { throwable -> + HelperUtil().parseError(this, throwable) + } + } + + /*private fun showAutoCloseDialog(context: Context?) { + AlertDialog.Builder(context!!) + .setMessage(R.string.auto_close) + .setPositiveButton(R.string.accept) { _, _ -> + workdayStatusViewModel.logout() + } + .setCancelable(false) + .show() + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/activities/WorkdayManagerActivity.kt b/app/src/main/java/com/iesoluciones/siodrenax/activities/WorkdayManagerActivity.kt new file mode 100644 index 0000000..dc3b2ac --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/activities/WorkdayManagerActivity.kt @@ -0,0 +1,105 @@ +package com.iesoluciones.siodrenax.activities + +import android.content.Intent +import android.os.Bundle +import android.telephony.PhoneNumberUtils +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.ViewModelProvider +import com.iesoluciones.mylibrary.activities.ToolbarActivity +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ActivityWorkdayBinding +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.ManagerRepository +import com.iesoluciones.siodrenax.utils.CircularProgressButton +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.goToActivity +import com.iesoluciones.siodrenax.viewmodels.ManagerViewModel +import java.text.SimpleDateFormat +import java.util.* + +class WorkdayManagerActivity : ToolbarActivity(), CircularProgressButton.ProgressListener { + + private lateinit var binding: ActivityWorkdayBinding + private lateinit var managerViewModel: ManagerViewModel + private lateinit var managerRepository: ManagerRepository + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityWorkdayBinding.inflate(layoutInflater) + setContentView(binding.root) + + @Suppress("USELESS_CAST") + val toolbar = binding.toolbar as Toolbar + toolbar.title = getString(R.string.t_WorkdayManagerActivity) + toolbarToLoad(toolbar) + + managerRepository = ManagerRepository() + binding.iniciarJornadaButton.progressListener = this + managerViewModel = ViewModelProvider(this)[ManagerViewModel::class.java] + + attachToObservables() + } + + override fun onProgressStart() {} + + override fun onProgressFinish() { + managerViewModel.startWorkload() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.workday, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.log_out -> { + managerRepository.logOut() + goToActivity { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + finish() + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToObservables() { + managerViewModel.onCallFailure.observe(this) { throwable: Throwable? -> + if (throwable != null) { + HelperUtil().parseError(this@WorkdayManagerActivity, throwable) + managerViewModel.onCallFailure.value = null + } + } + + managerViewModel.isLoading.observe(this) { s: String? -> + if (s != null) { + binding.frameLoading.visibility = View.VISIBLE + binding.tvLoading.text = s + binding.iniciarJornadaButton.deactivate() + } else { + binding.frameLoading.visibility = View.GONE + } + } + + managerViewModel.onWorkdaySuccess.observe(this) { success: Boolean -> + if (success) { + preferencesHelper.workdayStarted = true + goToActivity { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + } + } + + managerViewModel.getUser()!!.observe(this) { (_, _, username, lastName, mothersName, phone) -> + binding.tvUsername.text = getString(R.string.username, username, lastName, mothersName) + binding.tvInitials.text = username.substring(0, 1) + binding.tvPhone.text = PhoneNumberUtils.formatNumber(phone, "MX") + val inFormatter = SimpleDateFormat("dd 'de' MMM 'de' yyyy", Locale.getDefault()) + binding.tvDate.text = inFormatter.format(Date()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/HerramientaSurveyAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/HerramientaSurveyAdapter.kt new file mode 100644 index 0000000..62413ef --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/HerramientaSurveyAdapter.kt @@ -0,0 +1,111 @@ +package com.iesoluciones.siodrenax.adapters + +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.checkListQuestionBox +import com.iesoluciones.siodrenax.databinding.ViewholderHerramientaBinding +import com.iesoluciones.siodrenax.entities.CheckListQuestion +import com.iesoluciones.siodrenax.entities.CheckListQuestion_ +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.utils.Constants +import io.objectbox.android.AndroidScheduler +import io.objectbox.kotlin.equal +import io.objectbox.reactive.DataSubscription + +class HerramientaSurveyAdapter( + private var checkListQuestionList: List +) : RecyclerView.Adapter() { + + var checkListQuestionBoxObserve: DataSubscription? = null + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + + checkListQuestionBoxObserve = checkListQuestionBox.query( + CheckListQuestion_.tipo equal Constants.HERRAMIENTA + ) + .build() + .subscribe() + .on(AndroidScheduler.mainThread()) + .observer { checkListQuestion -> checkListQuestionList = checkListQuestion } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = ViewholderHerramientaBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return MyViewHolder(binding) + } + + override fun onBindViewHolder(holder: MyViewHolder, i: Int) { + holder.bind(checkListQuestionList[i]) + } + + override fun getItemCount() = checkListQuestionList.size + + class MyViewHolder(var binding: ViewholderHerramientaBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(checkListQuestion: CheckListQuestion) = with(itemView) { + itemView.tag = checkListQuestion.id + binding.tvQuestionTitle.text = checkListQuestion.nombre + + binding.lLCheck.visibility = + if (checkListQuestion.tipoCheckBox == 1) View.VISIBLE else View.GONE + binding.frameEdit.visibility = + if (checkListQuestion.tipoText == 1) View.VISIBLE else View.GONE + + binding.eTComentario.setText(checkListQuestion.respuestaText) + binding.cBQuestion.isChecked = checkListQuestion.respuestaCheckBox + + val isEnabled = preferencesHelper.isEnableHerramientaSurvey + + binding.eTComentario.isEnabled = isEnabled + binding.cBQuestion.isEnabled = isEnabled + + if (!isEnabled) { + binding.tvQuestionTitle.setTextColor( + ContextCompat.getColor( + context, + R.color.letraGrisBloqueado + ) + ) + } else { + listeners() + } + } + + private fun listeners() { + val id = itemView.tag.toString().toLong() + val objCheck: CheckListQuestion = checkListQuestionBox.get(id) + + if (objCheck.tipoText == 1) { + binding.eTComentario.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable) { + objCheck.respuestaText = s.toString() + checkListQuestionBox.put(objCheck) + } + }) + } + + if (objCheck.tipoCheckBox == 1) { + binding.cBQuestion.setOnCheckedChangeListener { _, isChecked -> + objCheck.respuestaCheckBox = isChecked + checkListQuestionBox.put(objCheck) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/MaterialSurveyAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/MaterialSurveyAdapter.kt new file mode 100644 index 0000000..6b5d1c9 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/MaterialSurveyAdapter.kt @@ -0,0 +1,76 @@ +package com.iesoluciones.siodrenax.adapters + +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.checkListQuestionBox +import com.iesoluciones.siodrenax.databinding.ViewholderMaterialBinding +import com.iesoluciones.siodrenax.entities.CheckListQuestion + +class MaterialSurveyAdapter( + private val checkListQuestionList: List +): RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val binding = ViewholderMaterialBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return MyViewHolder(binding) + } + + override fun onBindViewHolder(holder: MyViewHolder, i: Int) { + holder.bind(checkListQuestionList[i]) + } + + override fun getItemCount() = checkListQuestionList.size + + class MyViewHolder(var binding: ViewholderMaterialBinding) : RecyclerView.ViewHolder(binding.root) { + + fun bind(checkListQuestion: CheckListQuestion) = with(itemView) { + itemView.tag = checkListQuestion.id + binding.tvQuestionTitle.text = checkListQuestion.nombre + + binding.lLCheck.visibility = if (checkListQuestion.tipoCheckBox == 1) View.VISIBLE else View.GONE + binding.frameEdit.visibility = if (checkListQuestion.tipoText == 1) View.VISIBLE else View.GONE + + binding.eTComentario.setText(checkListQuestion.respuestaText) + binding.cBQuestion.isChecked = checkListQuestion.respuestaCheckBox + + listeners() + } + + private fun listeners() { + val id = itemView.tag.toString().toLong() + val objCheck: CheckListQuestion = checkListQuestionBox.get(id) + + binding.eTComentario.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence, + start: Int, + count: Int, + after: Int + ) { + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (objCheck.tipoText == 1) { + objCheck.respuestaText = binding.eTComentario.text.toString() + checkListQuestionBox.put(objCheck) + } + } + + override fun afterTextChanged(s: Editable) {} + }) + + binding.cBQuestion.setOnCheckedChangeListener { _, isChecked -> + objCheck.respuestaCheckBox = isChecked + checkListQuestionBox.put(objCheck) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/MultipleViewHolderAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/MultipleViewHolderAdapter.kt new file mode 100644 index 0000000..dfb47cb --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/MultipleViewHolderAdapter.kt @@ -0,0 +1,355 @@ +package com.iesoluciones.siodrenax.adapters + +import android.app.Activity +import android.app.DatePickerDialog +import android.text.Editable +import android.text.InputType +import android.text.TextWatcher +import android.util.Patterns +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnFocusChangeListener +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import androidx.appcompat.widget.AppCompatCheckBox +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ViewholderAnswerOpenBinding +import com.iesoluciones.siodrenax.databinding.ViewholderCheckboxAnswerBinding +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.models.Answer +import com.iesoluciones.siodrenax.models.Question +import com.iesoluciones.siodrenax.repositories.SurveyRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANSWER_CHECKBOX +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANSWER_CURRENCY +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANSWER_DATE +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANSWER_EMAIL +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANSWER_NUMBER +import com.iesoluciones.siodrenax.utils.Constants.Companion.ANSWER_TEXT +import com.iesoluciones.siodrenax.utils.Constants.Companion.CERO +import com.iesoluciones.siodrenax.utils.Constants.Companion.IDSERVICETYPEDOMESTIC +import java.text.NumberFormat +import java.util.* + +class MultipleViewHolderAdapter( + private var questionList: List, + private val order: Order +) : + RecyclerView.Adapter() { + + private val isDomestic = order.idServiceType == IDSERVICETYPEDOMESTIC + private val surveyRepository = SurveyRepository() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnswerViewHolder { + when (viewType) { + ANSWER_CHECKBOX -> return CheckBoxViewHolder( + ViewholderCheckboxAnswerBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + ANSWER_CURRENCY, ANSWER_DATE, ANSWER_EMAIL, ANSWER_NUMBER, ANSWER_TEXT -> return OpenAnswerViewHolder( + ViewholderAnswerOpenBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + viewType + ) + } + throw RuntimeException("Uncaught viewholder Type") + } + + override fun onBindViewHolder(holder: AnswerViewHolder, position: Int) { + holder.bindViews(questionList[position]) + } + + override fun getItemCount(): Int { + return questionList.size + } + + override fun getItemViewType(position: Int): Int { + return getViewType(questionList[position]) + } + + abstract class AnswerViewHolder(itemView: View) : + RecyclerView.ViewHolder(itemView) { + abstract fun bindViews(question: Question) + abstract fun cleanOnRecycled() + } + + inner class CheckBoxViewHolder(var binding: ViewholderCheckboxAnswerBinding) : + AnswerViewHolder(binding.root), View.OnClickListener { + private var tvQuestionTitle = binding.tvQuestionTitle + private var linearAnswer = binding.linearAnswer + private lateinit var answers: List + private lateinit var answer: Answer + private var checkBoxes: MutableList = ArrayList() + private lateinit var question: Question + + override fun bindViews(question: Question) { + this.question = question + + if (question.getShowNumber() == 0) + tvQuestionTitle.text = question.getTitle() + else + tvQuestionTitle.text = App.context!!.resources.getString( + R.string.question_title, + (adapterPosition + 1), + question.getTitle() + ) + + answers = surveyRepository.getAnswers(question, isDomestic) + for (answer in answers) { + val checkBox = AppCompatCheckBox(itemView.context) + checkBox.layoutParams = ViewGroup.LayoutParams( + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + ViewGroup.LayoutParams.WRAP_CONTENT.toFloat(), + itemView.context.resources.displayMetrics + ) + .toInt(), + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + ViewGroup.LayoutParams.WRAP_CONTENT.toFloat(), + itemView.context.resources.displayMetrics + ) + .toInt() + ) + checkBox.text = answer.getTitle() + checkBox.isChecked = false + checkBox.tag = answer + checkBox.setOnClickListener(this) + + val savedAnswer = surveyRepository.getQuestionSavedAnswer(question, order) + if (savedAnswer != null && savedAnswer != "" && savedAnswer.toLong() == answer.getId()) { + this.answer = answer + checkBox.isChecked = true + } + + linearAnswer.addView(checkBox) + checkBoxes.add(checkBox) + } + } + + override fun cleanOnRecycled() { + linearAnswer.removeAllViews() + } + + override fun onClick(v: View) { + if (!(v as AppCompatCheckBox).isChecked) { + surveyRepository.saveQuestionAnswer(question, order, null, false) + } else { + answer = v.getTag() as Answer + surveyRepository.saveQuestionAnswer( + question, + order, + answer.getId().toString() + "", + false + ) + } + + for (checkBox in checkBoxes) + if (checkBox.tag !== answer) + checkBox.isChecked = false + } + + } + + inner class OpenAnswerViewHolder( + var binding: ViewholderAnswerOpenBinding, + private var viewType: Int + ) : + AnswerViewHolder(binding.root), View.OnClickListener { + private var tvQuestionTitle = binding.tvQuestionTitle + private var frameDate = binding.frameDate + private var tvDate = binding.tvDate + private var editAnswer = binding.editAnswer + private var linearQuestion = binding.linearQuestion + private lateinit var question: Question + + //Calendario para obtener fecha & hora + private val c = Calendar.getInstance() + + //Variables para obtener la fecha + private val mes = c[Calendar.MONTH] + private val dia = c[Calendar.DAY_OF_MONTH] + private val anio = c[Calendar.YEAR] + + override fun bindViews(question: Question) { + this.question = question + if (question.getShowNumber() == 0) + tvQuestionTitle.text = question.getTitle() + else + tvQuestionTitle.text = App.context!!.resources.getString( + R.string.question_title, + (adapterPosition + 1), + question.getTitle() + ) + + when (viewType) { + ANSWER_CURRENCY -> { + //Currency TextWatcher, set + editAnswer.inputType = InputType.TYPE_CLASS_NUMBER + editAnswer.addTextChangedListener(CurrencyTextWatcher()) + editAnswer.setText("0") + editAnswer.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + var formatted = editAnswer.text.toString() + formatted = formatted.replace(",".toRegex(), "") + formatted = formatted.replace("\\$".toRegex(), "") + surveyRepository.saveQuestionAnswer( + question, + order, + formatted, + true + ) + } + } + } + ANSWER_DATE -> { + //Block clicks to show date picker, and format it + editAnswer.visibility = View.GONE + frameDate.visibility = View.VISIBLE + linearQuestion.setOnClickListener(this) + } + ANSWER_EMAIL -> { + //Email TextWatcher + editAnswer.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + editAnswer.imeOptions = EditorInfo.IME_ACTION_NEXT + editAnswer.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + if (!isValidEmail(editAnswer.text.toString())) editAnswer.error = + "Correo inválido" else surveyRepository.saveQuestionAnswer( + question, order, editAnswer.text.toString(), true + ) + } + } + } + ANSWER_NUMBER -> //Number formatter + editAnswer.inputType = InputType.TYPE_CLASS_NUMBER + ANSWER_TEXT -> { + //Text TextWatcher + editAnswer.imeOptions = EditorInfo.IME_ACTION_NEXT + editAnswer.onFocusChangeListener = + OnFocusChangeListener { _, hasFocus -> + var answers = editAnswer.text.toString() + answers = answers.replace(" ", "") + if (!hasFocus && answers != "") { + surveyRepository.saveQuestionAnswer( + question, + order, + editAnswer.text.toString(), + true + ) + } + } + + if (!editAnswer.hasFocus() && editAnswer.text.toString() == "") + surveyRepository.saveQuestionAnswer(question, order, null, true) + } + } + + val savedAnswer = surveyRepository.getQuestionSavedAnswer(question, order) + if (viewType == ANSWER_DATE) + tvDate.text = savedAnswer + else + editAnswer.setText(savedAnswer) + } + + override fun cleanOnRecycled() {} + private fun isValidEmail(target: String?): Boolean { + return if (target == null) { + false + } else { + //android Regex to check the email address Validation + Patterns.EMAIL_ADDRESS.matcher(target).matches() + } + } + + private fun obtenerFecha() { + val recogerFecha = DatePickerDialog( + itemView.context, + { _, year, month, dayOfMonth -> //Esta variable lo que realiza es aumentar en uno el mes ya que comienza desde 0 = enero + val mesActual = month + 1 + //Formateo el día obtenido: antepone el 0 si son menores de 10 + val diaFormateado = + if (dayOfMonth < 10) CERO + dayOfMonth.toString() else dayOfMonth.toString() + //Formateo el mes obtenido: antepone el 0 si son menores de 10 + val mesFormateado = + if (mesActual < 10) CERO + mesActual.toString() else mesActual.toString() + //Muestro la fecha con el formato deseado + val dateFormatted = App.context!!.resources.getString( + R.string.date_format, + year.toString(), + mesFormateado, + diaFormateado + ) + + tvDate.text = dateFormatted + surveyRepository.saveQuestionAnswer( + question, + order, + dateFormatted, + true + ) + }, //Estos valores deben ir en ese orden, de lo contrario no mostrara la fecha actual + anio, mes, dia + ) + //Muestro el widget + recogerFecha.show() + } + + internal inner class CurrencyTextWatcher : TextWatcher { + private var current = "" + + @Synchronized + override fun afterTextChanged(s: Editable) { + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (s.toString() != current) { + editAnswer.removeTextChangedListener(this) + val cleanString = s.toString().replace("[$,.]".toRegex(), "") + var parsed = 0.0 + try { + parsed = cleanString.toDouble() + } catch (ignored: NumberFormatException) { + } + val formatted = NumberFormat.getCurrencyInstance().format(parsed / 100) + current = formatted + editAnswer.setText(formatted) + editAnswer.setSelection(formatted.length) + editAnswer.addTextChangedListener(this) + } + } + } + + override fun onClick(v: View) { + val imm = + itemView.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(itemView.windowToken, 0) + obtenerFecha() + } + + } + + private fun getViewType(question: Question): Int { + return when (surveyRepository.getAnswers(question, isDomestic)[0].getType()) { + "Checkbox" -> ANSWER_CHECKBOX + "Fecha" -> ANSWER_DATE + "Email" -> ANSWER_EMAIL + "Numero" -> ANSWER_NUMBER + "Moneda" -> ANSWER_CURRENCY + "Texto" -> ANSWER_TEXT + else -> throw RuntimeException("Question Type Not Supported") + } + } +} diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/NextDayOrdersAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/NextDayOrdersAdapter.kt new file mode 100644 index 0000000..9bb8d02 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/NextDayOrdersAdapter.kt @@ -0,0 +1,159 @@ +package com.iesoluciones.siodrenax.adapters + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Context.VIBRATOR_SERVICE +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager +import android.telephony.PhoneNumberUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ViewholderOrderBinding +import com.iesoluciones.siodrenax.entities.NextDayOrder +import com.iesoluciones.siodrenax.utils.StatusDrawable +import com.iesoluciones.siodrenax.utils.currencyFormat +import java.text.SimpleDateFormat +import java.util.* + +class NextDayOrdersAdapter( + private val context: Context, + private var orders: List +) : RecyclerView.Adapter() { + + companion object { + val inFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + val outFormatter = SimpleDateFormat("HH:mm", Locale.getDefault()) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + ViewholderOrderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding, context) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(orders[position]) + } + + override fun getItemCount(): Int { + return orders.size + } + + fun setOrders(orders: List) { + this.orders = orders + } + + class ViewHolder( + val binding: ViewholderOrderBinding, + val context: Context + ) : RecyclerView.ViewHolder(binding.root) { + + private val tvClientDenomination = binding.tvClientDenomination + private val tvOrderName = binding.tvOrderName + private val tvPayMethod = binding.tvPayMethod + private val tvContact = binding.tvContact + private val tvAddress = binding.tvAddress + private val tvServiceStatus = binding.tvServiceStatus + private val tvTitle = binding.tvTitle + private val tvCost = binding.tvCost + private val tvOrderId = binding.tvOrderId + private val tvOrderScheduledTime = binding.tvOrderScheduledTime + private val tvContactPhone = binding.tvContactPhone + private val linearContact = binding.linearContact + private val linearComments = binding.linearComments + private val tvComments = binding.tvComments + private val tvVehicle = binding.tvVehicle + + fun bind(order: NextDayOrder) = with(itemView) { + try { + val temp: Date = inFormatter.parse(order.dateServiceRequest)!! + inFormatter.parse(order.dateServiceRequest)!! + tvClientDenomination.text = order.clientDenomination + tvOrderScheduledTime.text = context.resources.getString( + R.string.scheduled_time, outFormatter.format( + temp + ) + ) + } catch (e: Exception) { + e.printStackTrace() + tvClientDenomination.text = order.clientContactName + tvOrderScheduledTime.text = + context.resources.getString(R.string.scheduled_time_error) + } + + if (order.getClientContactCellphone.trim().isNotEmpty()) { + tvContact.text = order.clientContactName + tvContactPhone.text = context.resources.getString( + R.string.contact_phone, PhoneNumberUtils.formatNumber( + order.getClientContactCellphone, + "MX" + ) + ) + tvContactPhone.visibility = View.VISIBLE + } else { + tvContact.text = order.clientContactName + tvContactPhone.visibility = View.GONE + } + + if (order.comments != null) { + linearComments.visibility = View.VISIBLE + tvComments.text = order.comments + } else + linearComments.visibility = View.GONE + + tvOrderName.text = order.serviceName + tvServiceStatus.text = order.serviceStatusName + tvPayMethod.text = order.payMethodName + tvVehicle.text = order.vehicleCodeName + tvOrderId.text = "${order.idRequest}" + tvCost.text = context.resources.getString(R.string.cost, order.cost.currencyFormat()) + tvAddress.text = context.resources.getString( + R.string.address, + order.streetAddress, + order.extNumberAddress, + order.neighborhoodNameAddress, + order.clientPostalCodeAddress + ) + + val drawable = StatusDrawable(order.serviceStatusColor1, order.serviceStatusColor2) + tvTitle.background = drawable + + linearContact.setOnLongClickListener { + if (order.getClientContactCellphone.trim().isNotEmpty()) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((itemView.context as AppCompatActivity), arrayOf(Manifest.permission.CALL_PHONE), 3) + return@setOnLongClickListener true + } else { + val callIntent = Intent(Intent.ACTION_CALL) + callIntent.data = Uri.parse("tel:" + order.getClientContactCellphone.trim()) // change the number + vibrate() + context.startActivity(callIntent) + } + } + + false + } + } + + @SuppressLint("MissingPermission") + private fun vibrate() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = context.getSystemService(AppCompatActivity.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + }else{ + (context.getSystemService(VIBRATOR_SERVICE) as Vibrator).vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/OperatorAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/OperatorAdapter.kt new file mode 100644 index 0000000..98c3dfd --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/OperatorAdapter.kt @@ -0,0 +1,68 @@ +package com.iesoluciones.siodrenax.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ViewholderOperatorBinding +import com.iesoluciones.siodrenax.entities.Operator +import com.iesoluciones.siodrenax.utils.HelperUtil + +class OperatorAdapter( + var operatorList: List, + private val listener: OnOperatorSelectedListener + +) : RecyclerView.Adapter() { + + interface OnOperatorSelectedListener { + fun onOperatorClicked(operator: Operator) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + ViewholderOperatorBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding, listener) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(operatorList[position], position%2 == 0) + } + + override fun getItemCount(): Int { + return operatorList.size + } + + class ViewHolder( + val binding: ViewholderOperatorBinding, + val listener: OnOperatorSelectedListener + ): RecyclerView.ViewHolder(binding.root){ + + private val tvInitials = binding.tvInitials + private val tvName = binding.tvName + private val tvOrdersIndicator = binding.tvOrdersIndicator + private val cardOperator = binding.cardOperator + + fun bind(operator: Operator, colorFlag: Boolean) = with(itemView){ + tvName.text = "${operator.name} ${operator.lastName} ${operator.mothersName}" + + tvOrdersIndicator.text = "${operator.pendingOrders} / ${operator.totalOrders}" + + tvInitials.text = HelperUtil().getInitials( + operator.name, + operator.lastName + ) + + if (operator.pendingOrders == 0L) cardOperator.setCardBackgroundColor( + itemView.context.resources.getColor( + R.color.lightDarkGray + ) + ) else cardOperator.setCardBackgroundColor(itemView.context.resources.getColor(android.R.color.white)) + + if (colorFlag) tvInitials.setBackgroundResource(R.drawable.circle_border_accent) else tvInitials.setBackgroundResource( + R.drawable.circle_border_primary + ) + + itemView.setOnClickListener { listener.onOperatorClicked(operator) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/OrdersAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/OrdersAdapter.kt new file mode 100644 index 0000000..8b0d778 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/OrdersAdapter.kt @@ -0,0 +1,201 @@ +package com.iesoluciones.siodrenax.adapters + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Context.VIBRATOR_SERVICE +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager +import android.telephony.PhoneNumberUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.activities.OrderProgressActivity +import com.iesoluciones.siodrenax.databinding.ViewholderOrderBinding +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.entities.OrderProgress +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_ID +import com.iesoluciones.siodrenax.utils.StatusDrawable +import com.iesoluciones.siodrenax.utils.currencyFormat +import java.text.SimpleDateFormat +import java.util.* + +class OrdersAdapter( + private val context: Context, + var orders: List +) : RecyclerView.Adapter() { + + companion object{ + val inFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + val outFormatter = SimpleDateFormat("HH:mm", Locale.getDefault()) + val ordersRepository: OrdersRepository = OrdersRepository() + } + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + ViewholderOrderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding, context) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(orders[position]) + } + + override fun getItemCount(): Int { + return orders.size + } + + class ViewHolder( + val binding: ViewholderOrderBinding, + val context: Context + ): RecyclerView.ViewHolder(binding.root) { + + private val tvClientDenomination = binding.tvClientDenomination + private val tvOrderName = binding.tvOrderName + private val tvPayMethod = binding.tvPayMethod + private val tvContact = binding.tvContact + private val tvAddress = binding.tvAddress + private val tvServiceStatus = binding.tvServiceStatus + private val tvTitle = binding.tvTitle + private val tvCost = binding.tvCost + private val tvOrderId = binding.tvOrderId + private val tvOrderScheduledTime = binding.tvOrderScheduledTime + private val tvContactPhone = binding.tvContactPhone + private val linearContact = binding.linearContact + private val linearComments = binding.linearComments + private val tvComments = binding.tvComments + private val tvVehicle = binding.tvVehicle + + fun bind(order: Order) = with(itemView){ + try { + // NullPointerException launched once. The "dateServiceRequest" is a non null field. + // The WS sends the field and the Local Data Base saves it properly + // Commented to track the error -------------> Incidents = 1 + val temp: Date = inFormatter.parse(order.dateServiceRequest)!! + tvClientDenomination.text = order.clientDenomination + tvOrderScheduledTime.text = context.resources.getString( + R.string.scheduled_time, outFormatter.format( + temp + ) + ) + } catch (e: Exception) { + e.printStackTrace() + tvClientDenomination.text = order.clientContactName + tvOrderScheduledTime.text = context.resources.getString(R.string.scheduled_time_error) + } + + tvOrderName.text = order.serviceName + + val orderProgress: OrderProgress? = ordersRepository.getOrderProgressById(order.id) + tvServiceStatus.text = if(orderProgress != null) context.getString(R.string.sincronizando) else order.serviceStatusName + tvVehicle.text = order.vehicleCodeName + + + if (order.getClientContactCellphone.trim().isNotEmpty()) { + tvContact.text = order.clientContactName + tvContactPhone.text = context.resources.getString( + R.string.contact_phone, PhoneNumberUtils.formatNumber( + order.getClientContactCellphone, + "MX" + ) + ) + tvContactPhone.visibility = View.VISIBLE + } else { + tvContact.text = order.clientContactName + tvContactPhone.visibility = View.GONE + } + + tvPayMethod.text = order.payMethodName + + val drawable = StatusDrawable(order.serviceStatusColor1, order.serviceStatusColor2) + + tvTitle.background = drawable + + tvOrderId.text = "${order.idRequest}" + + tvCost.text = context.resources.getString(R.string.cost, order.cost.currencyFormat()) + + tvAddress.text = context.resources.getString( + R.string.address, + order.streetAddress, + order.extNumberAddress, + order.neighborhoodNameAddress, + order.clientPostalCodeAddress + ) + + if (order.comments != null) { + linearComments.visibility = View.VISIBLE + tvComments.text = order.comments + } else linearComments.visibility = View.GONE + + linearContact.setOnLongClickListener { + if (order.getClientContactCellphone.trim().isNotEmpty()) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((itemView.context as AppCompatActivity), arrayOf(Manifest.permission.CALL_PHONE), 3) + return@setOnLongClickListener true + }else{ + val callIntent = Intent(Intent.ACTION_CALL) + callIntent.data = Uri.parse("tel:" + order.getClientContactCellphone.trim()) // change the number + vibrate() + context.startActivity(callIntent) + } + } + false + } + + linearContact.setOnClickListener { + //Se agrega por error en Crashlytics + val orderRepo: Order? = ordersRepository.getOrderById(order.id) + //Se agrega por error en Crashlytics + if (orderRepo != null) { + changeToOrderProgressActivity(order.id) + } else { + Toast.makeText(context, "No se encontró la información.", Toast.LENGTH_SHORT).show() + } + } + + itemView.setOnClickListener { + //Se agrega por error en Crashlytics + val orderRepo: Order? = ordersRepository.getOrderById(order.id) + //Se agrega por error en Crashlytics + if (orderRepo != null) { + changeToOrderProgressActivity(order.id) + } else { + Toast.makeText(context, "No se encontró la información.", Toast.LENGTH_SHORT).show() + } + } + } + + private fun changeToOrderProgressActivity(orderId: Long){ + val i = Intent(context, OrderProgressActivity::class.java) + i.putExtra(ORDER_ID, orderId) + context.startActivity(i) + (context as AppCompatActivity).overridePendingTransition( + R.anim.slide_in_rigth_airbnb, + R.anim.scale_out_airbnb + ) + } + + @SuppressLint("MissingPermission") + private fun vibrate() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = context.getSystemService(AppCompatActivity.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + }else{ + (context.getSystemService(VIBRATOR_SERVICE) as Vibrator).vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/OrdersManagerAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/OrdersManagerAdapter.kt new file mode 100644 index 0000000..43dae05 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/OrdersManagerAdapter.kt @@ -0,0 +1,144 @@ +package com.iesoluciones.siodrenax.adapters + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager +import android.telephony.PhoneNumberUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.ViewholderOrderBinding +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.utils.StatusDrawable +import com.iesoluciones.siodrenax.utils.currencyFormat +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class OrdersManagerAdapter( + private val context: Context, + var orders: List +) : RecyclerView.Adapter() { + + companion object{ + var inFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + var outFormatter = SimpleDateFormat("HH:mm", Locale.getDefault()) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + ViewholderOrderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding, context) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(orders[position]) + } + + override fun getItemCount(): Int { + return orders.size + } + + class ViewHolder( + val binding: ViewholderOrderBinding, + val context: Context + ): RecyclerView.ViewHolder(binding.root) { + + fun bind(order: Order){ + try { + val temp: Date = inFormatter.parse(order.dateServiceRequest)!! + binding.tvClientDenomination.text = order.clientDenomination + binding.tvOrderScheduledTime.text = context.resources.getString( + R.string.scheduled_time, outFormatter.format( + temp + ) + ) + } catch (e: ParseException) { + e.printStackTrace() + binding.tvClientDenomination.text = order.clientContactName + binding.tvOrderScheduledTime.text = context.resources.getString(R.string.scheduled_time_error) + } + + binding.tvOrderName.text = order.serviceName + binding.tvServiceStatus.text = order.serviceStatusName + + if (order.getClientContactCellphone.trim().isNotEmpty()) { + binding.tvContact.text = order.clientContactName + binding.tvContactPhone.text = context.resources.getString( + R.string.contact_phone, PhoneNumberUtils.formatNumber( + order.getClientContactCellphone, + "MX" + ) + ) + binding.tvContactPhone.visibility = View.VISIBLE + } else { + binding.tvContact.text = order.clientContactName + binding.tvContactPhone.visibility = View.GONE + } + + binding.tvPayMethod.text = order.payMethodName + + val drawable = StatusDrawable(order.serviceStatusColor1, order.serviceStatusColor2) + + binding.tvTitle.background = drawable + + binding.tvVehicle.text = order.vehicleCodeName + + binding.tvOrderId.text = "${order.idRequest}" + + binding.tvCost.text = context.resources.getString( + R.string.cost, + order.cost.currencyFormat() + ) + + binding.tvAddress.text = context.resources.getString( + R.string.address, + order.streetAddress, + order.extNumberAddress, + order.neighborhoodNameAddress, + order.clientPostalCodeAddress + ) + + if (order.comments != null) { + binding.linearComments.visibility = View.VISIBLE + binding.tvComments.text = order.comments + } else binding.linearComments.visibility = View.GONE + + binding.linearContact.setOnLongClickListener { + if (order.getClientContactCellphone.trim().isNotEmpty()) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((itemView.context as AppCompatActivity), arrayOf(Manifest.permission.CALL_PHONE), 3) + return@setOnLongClickListener true + }else{ + val callIntent = Intent(Intent.ACTION_CALL) + callIntent.data = Uri.parse("tel:" + order.getClientContactCellphone.trim()) // change the number + vibrate() + context.startActivity(callIntent) + } + } + false + } + } + + @SuppressLint("MissingPermission") + private fun vibrate() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = context.getSystemService(AppCompatActivity.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator.vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + }else{ + (context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator).vibrate(VibrationEffect.createOneShot(150, VibrationEffect.DEFAULT_AMPLITUDE)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/PdfViewerAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/PdfViewerAdapter.kt new file mode 100644 index 0000000..e4270f1 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/PdfViewerAdapter.kt @@ -0,0 +1,59 @@ +package com.iesoluciones.siodrenax.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.databinding.ViewholderPdfBinding + +class PdfViewerAdapter( + private val context: Context, + var pages: List +): RecyclerView.Adapter() { + + class ViewHolder( + val binding: ViewholderPdfBinding, + val context: Context + ): RecyclerView.ViewHolder(binding.root), View.OnTouchListener { + + @SuppressLint("ClickableViewAccessibility") + fun bind(bitmap: Bitmap) = with(itemView){ + val imageView: ImageView = binding.imageView + imageView.setImageBitmap(bitmap) + imageView.setOnTouchListener(this@ViewHolder) + } + + override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean { + if(motionEvent!!.pointerCount >= 2 || (view!!.canScrollHorizontally(1) && view.canScrollHorizontally(-1))){ + when (motionEvent.action){ + MotionEvent.ACTION_MOVE -> { + view!!.parent.requestDisallowInterceptTouchEvent(true) + } + MotionEvent.ACTION_UP -> { + view!!.parent.requestDisallowInterceptTouchEvent(false) + } + else -> {return true} + } + } + return true + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = ViewholderPdfBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding, context) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(pages[position]) + } + + override fun getItemCount(): Int { + return pages.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/adapters/RevisionSurveyAdapter.kt b/app/src/main/java/com/iesoluciones/siodrenax/adapters/RevisionSurveyAdapter.kt new file mode 100644 index 0000000..7340d72 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/adapters/RevisionSurveyAdapter.kt @@ -0,0 +1,185 @@ +package com.iesoluciones.siodrenax.adapters + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnFocusChangeListener +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Button +import androidx.recyclerview.widget.RecyclerView +import com.iesoluciones.siodrenax.App.Companion.context +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.checkListQuestionBox +import com.iesoluciones.siodrenax.databinding.ViewholderRevisionBinding +import com.iesoluciones.siodrenax.entities.CheckListQuestion +import com.iesoluciones.siodrenax.entities.CheckListQuestion_ +import com.iesoluciones.siodrenax.repositories.CheckListRepository +import com.iesoluciones.siodrenax.utils.Constants +import io.objectbox.android.AndroidScheduler +import io.objectbox.kotlin.equal +import io.objectbox.reactive.DataSubscription + +class RevisionSurveyAdapter( + private val btnSiguiente: Button, + private var checkListQuestionList: List, + private val vehicle: List, + private val vehicleIds: List +) : RecyclerView.Adapter() { + + companion object { + var vehicleSelectedId: Long = 0 + } + + var checkListQuestionBoxObserve: DataSubscription? = null + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + + checkListQuestionBoxObserve = checkListQuestionBox.query( + CheckListQuestion_.tipo equal Constants.REVISION + ) + .build() + .subscribe() + .on(AndroidScheduler.mainThread()) + .observer { checkListQuestion -> checkListQuestionList = checkListQuestion } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + ViewholderRevisionBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding, vehicle, vehicleIds) + } + + override fun onBindViewHolder(holder: ViewHolder, i: Int) { + holder.bind(btnSiguiente, checkListQuestionList[i]) + } + + override fun getItemCount() = checkListQuestionList.size + + class ViewHolder( + var binding: ViewholderRevisionBinding, + private val vehicle: List, + val vehicleIds: List + ) : RecyclerView.ViewHolder(binding.root) { + + var isSpinnerTouched = false + + fun bind(btnSiguiente: Button, checkListQuestion: CheckListQuestion) = with(itemView) { + + binding.radioGroup.visibility = + if (checkListQuestion.tipoRadioBtn == 1) View.VISIBLE else View.GONE + + binding.eTComentario.visibility = + if (checkListQuestion.tipoText == 1) View.VISIBLE else View.GONE + + binding.spVehiculos.visibility = + if (checkListQuestion.id == 1L) View.VISIBLE else View.GONE + + val isTipoSpinner = + checkListQuestion.tipoCheckBox == 0 && checkListQuestion.tipoRadioBtn == 0 && checkListQuestion.tipoText == 0 + if (isTipoSpinner) { + val dataAdapter: ArrayAdapter = ArrayAdapter( + context, + android.R.layout.simple_spinner_item, + vehicle + ) + dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.spVehiculos.adapter = dataAdapter + if (checkListQuestion.respuestaText != "") { + for (i in vehicle.indices) { + if (vehicle[i] == checkListQuestion.respuestaText) { + binding.spVehiculos.setSelection(i) + } + } + } + } + + itemView.tag = checkListQuestion.id + binding.tvQuestionTitle.text = checkListQuestion.nombre + binding.eTComentario.setText(checkListQuestion.respuestaText) + val radioCheck: String? = checkListQuestion.respuestaRadioBtn + if (radioCheck != null && itemView.tag == checkListQuestion.id) { + + binding.radioNormal.tag = checkListQuestion.id + binding.radioBajo.tag = checkListQuestion.id + + if (radioCheck == context.getString(R.string.normal)) { + binding.radioGroup.check(R.id.radioNormal) + } else if (radioCheck == context.getString(R.string.bajo)) { + binding.radioGroup.check(R.id.radioBajo) + } + } + + listeners(isTipoSpinner, btnSiguiente) + } + + @SuppressLint("ClickableViewAccessibility") + private fun listeners(isTipoSpinner: Boolean, btnSiguiente: Button) { + val id = itemView.tag.toString().toLong() + val objCheck: CheckListQuestion = checkListQuestionBox.get(id) + + if(objCheck.tipoText == 1){ + binding.eTComentario.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + objCheck.respuestaText = (binding.eTComentario.text.toString()) + checkListQuestionBox.put(objCheck) + } + } + } + + if(objCheck.tipoRadioBtn == 1){ + binding.radioBajo.setOnClickListener { + setCheckListText(objCheck, btnSiguiente, context!!.getString(R.string.bajo)) + } + + binding.radioNormal.setOnClickListener { + setCheckListText(objCheck, btnSiguiente, context!!.getString(R.string.normal)) + } + } + + if (isTipoSpinner) { + btnSiguiente.visibility = if (CheckListRepository().isValidRevision() && binding.spVehiculos.selectedItemPosition != 0) View.VISIBLE else View.GONE + binding.spVehiculos.setOnTouchListener { _, _ -> + isSpinnerTouched = true + false + } + + binding.spVehiculos.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>, + view: View, + position: Int, + id: Long + ) { + vehicleSelectedId = vehicleIds[position] + if (isSpinnerTouched) { + val item = parent.getItemAtPosition(position).toString() + objCheck.respuestaText = (if (position != 0) item else "") + checkListQuestionBox.put(objCheck) + isSpinnerTouched = false + btnSiguiente.visibility = if (CheckListRepository().isValidRevision() && vehicleSelectedId != 0L) View.VISIBLE else View.GONE + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + } + } + + private fun setCheckListText(objCheck:CheckListQuestion, btnSiguiente:Button, text:String){ + objCheck.respuestaRadioBtn = text + checkListQuestionBox.put(objCheck) + btnSiguiente.visibility = if (CheckListRepository().isValidRevision() && vehicleSelectedId != 0L) View.VISIBLE else View.GONE + } + } + + override fun onViewRecycled(holder: ViewHolder) { + super.onViewRecycled(holder) + holder.binding.radioGroup.setOnCheckedChangeListener(null) + holder.binding.spVehiculos.onItemSelectedListener = null + holder.binding.radioGroup.clearCheck() + } +} diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/BusinessAnswer.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/BusinessAnswer.kt new file mode 100644 index 0000000..3444860 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/BusinessAnswer.kt @@ -0,0 +1,54 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.models.Answer +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb + +@Entity +data class BusinessAnswer( + @Id(assignable = true) + private var id: Long, + + @NameInDb("pregunta_id") + @SerializedName("pregunta_id") + private var idQuestion: Long, + + @NameInDb("nombre") + @SerializedName("nombre") + private var title: String, + + @NameInDb("orden") + @SerializedName("orden") + private var order: Int, + + @NameInDb("tipo_campo") + @SerializedName("tipo_campo") + private var type: String, + +): Answer() { + override fun getId(): Long { + return id + } + + override fun getIdQuestion(): Long { + return idQuestion + } + + override fun getTitle(): String { + return title + } + + override fun getOrder(): Int { + return order + } + + override fun getType(): String { + return type + } + + fun setId(id: Long){ + this.id = id + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/BusinessQuestion.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/BusinessQuestion.kt new file mode 100644 index 0000000..1acb0d5 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/BusinessQuestion.kt @@ -0,0 +1,54 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.models.Question +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb + +@Entity +data class BusinessQuestion( + @Id(assignable = true) + private var id: Long, + + @NameInDb("nombre") + @SerializedName("nombre") + private var title: String, + + @NameInDb("orden") + @SerializedName("orden") + private var order: Int, + + @NameInDb("mostrar_numero") + @SerializedName("mostrar_numero") + private var showNumber: Int, + + @NameInDb("obligatorio") + @SerializedName("obligatorio") + private var required: Boolean, + +): Question() { + override fun getId(): Long { + return id + } + + override fun getTitle(): String { + return title + } + + override fun getOrder(): Int { + return order + } + + override fun getShowNumber(): Int { + return showNumber + } + + override fun getRequired(): Boolean { + return required + } + + fun setId(id: Long){ + this.id = id + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/CheckListQuestion.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/CheckListQuestion.kt new file mode 100644 index 0000000..8a145c4 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/CheckListQuestion.kt @@ -0,0 +1,51 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb +import javax.annotation.Nullable + +@Entity +data class CheckListQuestion( + @Id(assignable = true) + var id: Long, + + var nombre: String, + + @NameInDb("tipo_radio_btn") + @SerializedName("tipo_radio_btn") + var tipoRadioBtn: Int, + + @NameInDb("tipo_text") + @SerializedName("tipo_text") + var tipoText: Int, + + @NameInDb("tipo_checkbox") + @SerializedName("tipo_checkbox") + var tipoCheckBox: Int, + + @NameInDb("tipo") + @SerializedName("tipo") + var tipo: String, + + @Nullable + @NameInDb("respuesta_radio_btn") + @SerializedName("respuesta_radio_btn") + var respuestaRadioBtn: String? = null, + + @Nullable + @NameInDb("respuesta_text") + @SerializedName("respuesta_text") + var respuestaText: String? = null, + + @Nullable + @NameInDb("respuesta_checkbox") + @SerializedName("respuesta_checkbox") + var respuestaCheckBox: Boolean = false, + + @Nullable + @NameInDb("fecha") + @SerializedName("fecha") + var fecha: String +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/DomesticAnswer.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/DomesticAnswer.kt new file mode 100644 index 0000000..1fec2f2 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/DomesticAnswer.kt @@ -0,0 +1,52 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.models.Answer +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb + +@Entity +data class DomesticAnswer( + @Id(assignable = true) + private var id: Long, + + @NameInDb("pregunta_id") + @SerializedName("pregunta_id") + private var idQuestion: Long, + + @NameInDb("nombre") + @SerializedName("nombre") + private var title: String, + + @NameInDb("orden") + @SerializedName("orden") + private var order: Int, + + @NameInDb("tipo_campo") + @SerializedName("tipo_campo") + private var type: String, +): Answer() { + override fun getId(): Long { + return id + } + + override fun getIdQuestion(): Long { + return idQuestion + } + + override fun getTitle(): String { + return title + } + + override fun getOrder(): Int { + return order + } + + override fun getType(): String { + return type + } + fun setId(id: Long){ + this.id = id + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/DomesticQuestion.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/DomesticQuestion.kt new file mode 100644 index 0000000..b2fc3ac --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/DomesticQuestion.kt @@ -0,0 +1,54 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.models.Question +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb + +@Entity +class DomesticQuestion( + @Id(assignable = true) + private var id: Long, + + @NameInDb("nombre") + @SerializedName("nombre") + private var title: String, + + @NameInDb("orden") + @SerializedName("orden") + private var order: Int, + + @NameInDb("mostrar_numero") + @SerializedName("mostrar_numero") + private var showNumber: Int, + + @NameInDb("obligatorio") + @SerializedName("obligatorio") + private var required: Boolean, + +):Question() { + override fun getId(): Long { + return id + } + + override fun getTitle(): String { + return title + } + + override fun getOrder(): Int { + return order + } + + override fun getShowNumber(): Int { + return showNumber + } + + override fun getRequired(): Boolean { + return required + } + + fun setId(id: Long){ + this.id = id + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/Evidence.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/Evidence.kt new file mode 100644 index 0000000..a63e1c6 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/Evidence.kt @@ -0,0 +1,48 @@ +package com.iesoluciones.siodrenax.entities + +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LATITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.DEFAULT_LONGITUDE +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_FINAL +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_PROCESS +import com.iesoluciones.siodrenax.utils.Constants.Companion.EVIDENCE_START +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class Evidence( + @Id + var id: Long, + var evidenceNo: Long, + var idOrder: Long, + var idRequest: Long, + var type: Int, + var path: String?, + var name: String?, + var lat: String, + var lng: String, + var viewRef: Int, + var isSent: Boolean +){ + constructor() : this( + 0, + 0, + 0, + 0, + 0, + null, + null, + DEFAULT_LATITUDE.toString(), + DEFAULT_LONGITUDE.toString(), + 0, + false + ) + + fun getTypeDescription(): String { + return when (this.type) { + EVIDENCE_START -> "Inicio" + EVIDENCE_PROCESS -> "Proceso" + EVIDENCE_FINAL -> "Final" + else -> "" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/NegativeServiceReason.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/NegativeServiceReason.kt new file mode 100644 index 0000000..fdc0e4c --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/NegativeServiceReason.kt @@ -0,0 +1,29 @@ +package com.iesoluciones.siodrenax.entities + +import com.iesoluciones.siodrenax.models.Reason +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class NegativeServiceReason( + @Id(assignable = true) + private var id: Long, + private var descripcion: String + +) : Reason() { + override fun getId(): Long { + return id + } + + override fun getTitle(): String { + return descripcion + } + + fun setId(id: Long){ + this.id = id + } + + fun getDescripcion(): String{ + return descripcion + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/NextDayOrder.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/NextDayOrder.kt new file mode 100644 index 0000000..73ea026 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/NextDayOrder.kt @@ -0,0 +1,167 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class NextDayOrder ( + @Id(assignable = true) + @SerializedName("id") + var id: Long, + + @SerializedName("servicio_id") + var idService: Long, + + @SerializedName("solicitud_servicio_id") + var idRequest: Long, + + @SerializedName("servicio_nombre") + var serviceName: String, + + @SerializedName("estatus_servicio_id") + var idServiceStatus: Long, + + @SerializedName("estatus_servicio_nombre") + var serviceStatusName: String, + + @SerializedName("estatus_servicio_color_1") + var serviceStatusColor1: String, + + @SerializedName("estatus_servicio_color_2") + var serviceStatusColor2: String? = null, + + @SerializedName("forma_pago_id") + var idPayMethod: Long, + + @SerializedName("forma_pago_nombre") + var payMethodName: String, + + @SerializedName("tipo_servicio_id") + var idServiceType: Long, + + @SerializedName("observacion_atencion_cliente") + var comments: String?, + + @SerializedName("costo_servicio") + var cost: String, + + @SerializedName("tipo_servicio_nombre") + var serviceTypeName: String, + + @SerializedName("fecha_agenda") + var dateScheduled: String, + + @SerializedName("fecha_solicitud") + var dateServiceRequest: String, + + @SerializedName("usuario_agenda_id") + var idUserSchedule: Long, + + @SerializedName("usuario_agenda_nombre") + var userScheduleName: String, + + @SerializedName("usuario_agenda_apellido_paterno") + var userScheduleLastName: String, + + @SerializedName("usuario_agenda_apellido_materno") + var getUserScheduleMothersLastName: String, + + @SerializedName("duracion") + var duracion: String, + + @SerializedName("definido_cliente") + var clientDefined: Long, + + @SerializedName("cliente_id") + var idClient: Long, + + @SerializedName("denominacion") + var clientDenomination: String, + + @SerializedName("clientes_nombre_responsable_sucursal") + var clientContactName: String, + + @SerializedName("clientes_celular_responsable") + var getClientContactCellphone: String, + + @SerializedName("cliente_domicilio_id") + var idClientAddress: Long, + + @SerializedName("clientes_calle") + var streetAddress: String? = null, + + @SerializedName("clientes_num_ext") + var extNumberAddress: String? = null, + + @SerializedName("clientes_num_int") + var intNumberAddress: String? = null, + + @SerializedName("clientes_colonia") + var neighborhoodNameAddress: String? = null, + + @SerializedName("clientes_cp") + var clientPostalCodeAddress: String? = null, + + @SerializedName("clientes_telefono") + var clientPhone: String, + + @SerializedName("clientes_lat") + var clientLat: String, + + @SerializedName("clientes_lng") + var clientLng: String, + + @SerializedName("operador_id") + var idOperator: Long, + + @SerializedName("operador_nombre") + var operatorName: String, + + @SerializedName("operador_apellido_paterno") + var operatorLastName: String, + + @SerializedName("operador_apellido_materno") + var operatorMothersName: String, + + @SerializedName("vehiculo_id") + var idVehicle: Long, + + @SerializedName("vehiculo_num_economico") + var vehicleCodeName: String, + + @SerializedName("vehiculo_sucursal") + var vehicleOfficeName: String, + + @SerializedName("vehiculo_sucursal_id") + var idVehicleOffice: Long, + + @SerializedName("operador_sucursal") + var operatorOfficeName: String, + + @SerializedName("operador_sucursal_id") + var idOperatorOffice: Long, + + @SerializedName("auxiliar_1_id") + var idAssistant1: Long, + + @SerializedName("auxiliar_2_id") + var idAssistant2: Long, + + @SerializedName("auxiliar_1") + var assistant1Name: String? = null, + + @SerializedName("auxiliar_2") + var assistant2Name: String? = null, + + @SerializedName("sucursal_auxiliar_1") + var assistant1OfficeName: String? = null, + + @SerializedName("sucursal_auxiliar_2") + var assistant2OfficeName: String? = null, + + @SerializedName("requiere_encuesta") + var SurveyRequired: Int, + + var isSent: Boolean, +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/Operator.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/Operator.kt new file mode 100644 index 0000000..d922f29 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/Operator.kt @@ -0,0 +1,31 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb + +@Entity +data class Operator( + @Id(assignable = true) var id: Long, + + @NameInDb("nombre") + @SerializedName("nombre") + var name: String, + + @NameInDb("apellido_paterno") + @SerializedName("apellido_paterno") + var lastName: String, + + @NameInDb("apellido_materno") + @SerializedName("apellido_materno") + var mothersName: String, + + @NameInDb("servicios_total") + @SerializedName("servicios_total") + var totalOrders: Long, + + @NameInDb("servicios_pendiente") + @SerializedName("servicios_pendiente") + var pendingOrders: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/Order.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/Order.kt new file mode 100644 index 0000000..b9a6aa0 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/Order.kt @@ -0,0 +1,190 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class Order ( + @Id(assignable = true) + @SerializedName("id") + var id: Long, + + @SerializedName("servicio_id") + var idService: Long, + + @SerializedName("solicitud_servicio_id") + var idRequest: Long, + + @SerializedName("servicio_nombre") + var serviceName: String, + + @SerializedName("estatus_servicio_id") + var idServiceStatus: Long, + + @SerializedName("estatus_servicio_nombre") + var serviceStatusName: String, + + @SerializedName("estatus_servicio_color_1") + var serviceStatusColor1: String, + + @SerializedName("estatus_servicio_color_2") + var serviceStatusColor2: String? = null, + + @SerializedName("forma_pago_id") + var idPayMethod: Long, + + @SerializedName("forma_pago_nombre") + var payMethodName: String, + + @SerializedName("tipo_servicio_id") + var idServiceType: Long, + + @SerializedName("observacion_atencion_cliente") + var comments: String?, + + @SerializedName("costo_servicio") + var cost: String, + + @SerializedName("costo_servicio_negativo") + var negativeCost: String, + + @SerializedName("tipo_servicio_nombre") + var serviceTypeName: String, + + @SerializedName("fecha_agenda") + var dateScheduled: String, + + @SerializedName("fecha_solicitud") + var dateServiceRequest: String, + + @SerializedName("usuario_agenda_id") + var idUserSchedule: Long, + + @SerializedName("usuario_agenda_nombre") + var userScheduleName: String, + + @SerializedName("usuario_agenda_apellido_paterno") + var userScheduleLastName: String, + + @SerializedName("usuario_agenda_apellido_materno") + var getUserScheduleMothersLastName: String, + + @SerializedName("duracion") + var duracion: String, + + @SerializedName("definido_cliente") + var clientDefined: Long, + + @SerializedName("cliente_id") + var idClient: Long, + + @SerializedName("denominacion") + var clientDenomination: String, + + @SerializedName("clientes_nombre_responsable_sucursal") + var clientContactName: String, + + @SerializedName("clientes_celular_responsable") + var getClientContactCellphone: String, + + @SerializedName("cliente_domicilio_id") + var idClientAddress: Long, + + @SerializedName("clientes_calle") + var streetAddress: String? = null, + + @SerializedName("clientes_num_ext") + var extNumberAddress: String? = null, + + @SerializedName("clientes_num_int") + var intNumberAddress: String? = null, + + @SerializedName("clientes_colonia") + var neighborhoodNameAddress: String? = null, + + @SerializedName("clientes_cp") + var clientPostalCodeAddress: String? = null, + + @SerializedName("clientes_ciudad") + var clientCity: String, + + @SerializedName("clientes_telefono") + var clientPhone: String, + + @SerializedName("clientes_lat") + var clientLat: String, + + @SerializedName("clientes_lng") + var clientLng: String, + + @SerializedName("operador_id") + var idOperator: Long, + + @SerializedName("operador_nombre") + var operatorName: String, + + @SerializedName("operador_apellido_paterno") + var operatorLastName: String, + + @SerializedName("operador_apellido_materno") + var operatorMothersName: String, + + @SerializedName("vehiculo_id") + var idVehicle: Long, + + @SerializedName("vehiculo_num_economico") + var vehicleCodeName: String, + + @SerializedName("vehiculo_sucursal") + var vehicleOfficeName: String, + + @SerializedName("vehiculo_sucursal_id") + var idVehicleOffice: Long, + + @SerializedName("operador_sucursal") + var operatorOfficeName: String, + + @SerializedName("operador_sucursal_id") + var idOperatorOffice: Long, + + @SerializedName("auxiliar_1_id") + var idAssistant1: Long, + + @SerializedName("auxiliar_2_id") + var idAssistant2: Long, + + @SerializedName("auxiliar_1") + var assistant1Name: String? = null, + + @SerializedName("auxiliar_1_apellido_paterno") + var assistant1LastName: String? = null, + + @SerializedName("auxiliar_1_apellido_materno") + var assistant1MothersName: String? = null, + + @SerializedName("auxiliar_2") + var assistant2Name: String? = null, + + @SerializedName("sucursal_auxiliar_1") + var assistant1OfficeName: String? = null, + + @SerializedName("sucursal_auxiliar_2") + var assistant2OfficeName: String? = null, + + @SerializedName("requiere_encuesta") + var SurveyRequired: Int, + + var isSent: Boolean, + + @SerializedName("pdf_path") + var pdfPath: String?, + + @SerializedName("nombre_croquis") + var sketchName: String?, + + var sketchPath: String?, + + @SerializedName("nombre_sucursal") + var branchName: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/OrderProgress.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/OrderProgress.kt new file mode 100644 index 0000000..72ff7c5 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/OrderProgress.kt @@ -0,0 +1,54 @@ +package com.iesoluciones.siodrenax.entities + +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class OrderProgress ( + + @Id(assignable = true) + var id: Long, + var idRequest: Long, + var startDate: String, + var endDate: String?, + var comments: String?, + var elapsedTime: String?, + var startLat: String, + var startLng: String, + var endLat: String, + var endLng: String, + var signaturePath: String?, + var signatureName: String?, + var warranty: Boolean, + var isSent: Boolean, + var shouldBeSent: Boolean, + var signatureSent: Boolean, + var isOnSurvey: Boolean, + var shouldSendSurvey: Boolean, + var surveySent: Boolean, + var liters: Int, + var negativeService: Int, +){ + constructor() : this(0, + 0, + "", + null, + "", + null, + "", + "", + "", + "", + null, + null, + false, + false, + false, + false, + false, + false, + false, + 0, + 0 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/SavedAnswer.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/SavedAnswer.kt new file mode 100644 index 0000000..3a1cb31 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/SavedAnswer.kt @@ -0,0 +1,21 @@ +package com.iesoluciones.siodrenax.entities + +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class SavedAnswer( + @Id(assignable = true) + var id: Long, + var questionId: Long, + var orderId: Long, + var isOpen: Boolean, + var answer: String? +){ + constructor() : this(0, + 0, + 0, + false, + null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/User.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/User.kt new file mode 100644 index 0000000..c3a12ab --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/User.kt @@ -0,0 +1,32 @@ +package com.iesoluciones.siodrenax.entities + +import com.google.gson.annotations.SerializedName +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.annotation.NameInDb + +@Entity +data class User( + @Id(assignable = true) var id: Long, + var email: String, + + @NameInDb("nombre") + @SerializedName("nombre") + var username: String, + + @NameInDb("apellido_paterno") + @SerializedName("apellido_paterno") + var lastName: String, + + @NameInDb("apellido_materno") + @SerializedName("apellido_materno") + var mothersName: String, + + @NameInDb("telefono") + @SerializedName("telefono") + var phone: String, + + @NameInDb("tipo_empleado_id") + @SerializedName("tipo_empleado_id") + var idEmployeeType: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/entities/Vehicle.kt b/app/src/main/java/com/iesoluciones/siodrenax/entities/Vehicle.kt new file mode 100644 index 0000000..d14bed5 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/entities/Vehicle.kt @@ -0,0 +1,10 @@ +package com.iesoluciones.siodrenax.entities + +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id + +@Entity +data class Vehicle( + @Id(assignable = true) var id: Long, + var nombre: String +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/IncidentsInputFragment.kt b/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/IncidentsInputFragment.kt new file mode 100644 index 0000000..26debe6 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/IncidentsInputFragment.kt @@ -0,0 +1,82 @@ +package com.iesoluciones.siodrenax.fragments.dialogs + +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.FragmentIncidentsInputBinding +import com.iesoluciones.siodrenax.interfaces.OnIncidentListener + +class IncidentsInputFragment : DialogFragment(){ + + private lateinit var _binding: FragmentIncidentsInputBinding + private val binding get() = _binding + lateinit var listener: OnIncidentListener + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + + _binding = FragmentIncidentsInputBinding.inflate(inflater, container, false) + + binding.tvContinue.setOnClickListener { onClickTvContinue() } + binding.tvCancel.setOnClickListener { onClickTvCancel() } + + return binding.root + } + + private fun onClickTvContinue() { + val editIncident = binding.editIncident + + if (isEmpty(editIncident)) { + binding.labelIncident.error = getText(R.string.incident_dialog_error) + listener.onIncidentInput(null) + } else { + listener.onIncidentInput(editIncident.text.toString().replace(",".toRegex(), "")) + dismiss() + } + } + + private fun onClickTvCancel() { + listener.onNoIncident() + dismiss() + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + val ft = manager.beginTransaction() + ft.add(this, tag) + ft.commitAllowingStateLoss() + } catch (e: IllegalStateException) { + Log.e("Exception", e.toString()) + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + + listener = if (activity is OnIncidentListener) { + (activity as OnIncidentListener?)!! + } else throw RuntimeException("Parent Activity not implementing OnIncidentListener") + } + + private fun isEmpty(incident: EditText): Boolean { + return incident.text.toString().trim { it <= ' ' }.isEmpty() || incident.text == null + } + + companion object { + fun newInstance(listener: OnIncidentListener): IncidentsInputFragment { + val fragment = IncidentsInputFragment() + fragment.listener = listener + return fragment + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/MileageInputDialogFragment.kt b/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/MileageInputDialogFragment.kt new file mode 100644 index 0000000..01c5d68 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/MileageInputDialogFragment.kt @@ -0,0 +1,86 @@ +package com.iesoluciones.siodrenax.fragments.dialogs + +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.databinding.FragmentMileageInputDialogBinding +import com.iesoluciones.siodrenax.interfaces.OnMileageListener +import com.iesoluciones.siodrenax.utils.NumberFormatterTextWatcher + +class MileageInputDialogFragment : DialogFragment() { + + private lateinit var _binding: FragmentMileageInputDialogBinding + private val binding get() = _binding + lateinit var listener: OnMileageListener + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentMileageInputDialogBinding.inflate(inflater, container, false) + val view = binding.root + isCancelable = false + binding.labelMileage.suffixText = getText(R.string.mileage_dialog_suffix) + + binding.tvContinue.setOnClickListener { + if (isEmpty(binding.editMileage)) { + binding.labelMileage.error = getText(R.string.mileage_dialog_error) + listener.onMileageInput(null) + } else { + listener.onMileageInput( + binding.editMileage.text.toString().replace(",".toRegex(), "") + ) + dismiss() + } + } + + binding.tvCancel.setOnClickListener { + dismiss() + } + + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.labelMileage.suffixText = getText(R.string.mileage_dialog_suffix) + binding.editMileage.addTextChangedListener(NumberFormatterTextWatcher(binding.editMileage)) + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + val ft = manager.beginTransaction() + ft.add(this, tag) + ft.commitAllowingStateLoss() + } catch (e: IllegalStateException) { + Log.e("Exception", e.toString()) + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + listener = if (activity is OnMileageListener) { + activity as OnMileageListener + } else throw RuntimeException("Parent Activity not implementing OnMileageListener") + } + + private fun isEmpty(mileage: EditText?): Boolean { + return mileage!!.text.toString().trim { it <= ' ' }.isEmpty() || mileage.text == null + } + + companion object { + fun newInstance(listener: OnMileageListener): MileageInputDialogFragment { + val fragment = MileageInputDialogFragment() + fragment.listener = listener + return fragment + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/VehicleIncidentsFragment.kt b/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/VehicleIncidentsFragment.kt new file mode 100644 index 0000000..4e53000 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/fragments/dialogs/VehicleIncidentsFragment.kt @@ -0,0 +1,66 @@ +package com.iesoluciones.siodrenax.fragments.dialogs + +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import com.iesoluciones.siodrenax.databinding.FragmentVehicleIncidentsBinding +import com.iesoluciones.siodrenax.interfaces.OnIncidentShowListener + +class VehicleIncidentsFragment : DialogFragment() { + + private lateinit var _binding: FragmentVehicleIncidentsBinding + private val binding get() = _binding + lateinit var listener: OnIncidentShowListener + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = FragmentVehicleIncidentsBinding.inflate(inflater, container, false) + val view = binding.root + + binding.tvIncidence.text = incidence + isCancelable = false + + binding.tvContinue.setOnClickListener{ + listener.onIncidenceSolved() + dismiss() + } + + binding.tvCancel.setOnClickListener{ + listener.onIncidenceNotSolved() + dismiss() + } + + return view + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + val ft = manager.beginTransaction() + ft.add(this, tag) + ft.commitAllowingStateLoss() + } catch (e: IllegalStateException) { + Log.e("Exception", e.toString()) + } + } + + override fun onAttach(context: Context) { + super.onAttach(context) + listener = if (activity is OnIncidentShowListener) { + activity as OnIncidentShowListener + } else throw RuntimeException("Parent Activity not implementing OnIncidentListener") + } + + companion object { + var incidence: String? = null + fun newInstance(listener: OnIncidentShowListener, incidence: String?): VehicleIncidentsFragment { + val fragment = VehicleIncidentsFragment() + fragment.listener = listener + Companion.incidence = incidence + return fragment + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnIncidentListener.kt b/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnIncidentListener.kt new file mode 100644 index 0000000..ce1e5b2 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnIncidentListener.kt @@ -0,0 +1,6 @@ +package com.iesoluciones.siodrenax.interfaces + +interface OnIncidentListener { + fun onNoIncident() + fun onIncidentInput(incident: String?) +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnIncidentShowListener.kt b/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnIncidentShowListener.kt new file mode 100644 index 0000000..8dacd7a --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnIncidentShowListener.kt @@ -0,0 +1,6 @@ +package com.iesoluciones.siodrenax.interfaces + +interface OnIncidentShowListener { + fun onIncidenceSolved() + fun onIncidenceNotSolved() +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnMileageListener.kt b/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnMileageListener.kt new file mode 100644 index 0000000..ddda224 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/interfaces/OnMileageListener.kt @@ -0,0 +1,5 @@ +package com.iesoluciones.siodrenax.interfaces + +interface OnMileageListener { + fun onMileageInput(mileage: String?) +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/Answer.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/Answer.kt new file mode 100644 index 0000000..d932ddc --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/Answer.kt @@ -0,0 +1,10 @@ +package com.iesoluciones.siodrenax.models + +abstract class Answer { + abstract fun getId(): Long + abstract fun getIdQuestion(): Long + abstract fun getTitle(): String + abstract fun getOrder(): Int + abstract fun getType(): String + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/AnswerPOJO.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/AnswerPOJO.kt new file mode 100644 index 0000000..232e5ac --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/AnswerPOJO.kt @@ -0,0 +1,30 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.entities.BusinessAnswer +import com.iesoluciones.siodrenax.entities.DomesticAnswer + +data class AnswerPOJO( + @SerializedName("id") + var id: Long, + + @SerializedName("pregunta_id") + var idQuestion: Long, + + @SerializedName("nombre") + var title: String, + + @SerializedName("orden") + var order: Int, + + @SerializedName("tipo_campo") + var type: String, +){ + fun getDomesticAnswer(): DomesticAnswer { + return DomesticAnswer(id, idQuestion, title, order, type) + } + + fun getBusinessAnswer(): BusinessAnswer { + return BusinessAnswer(id, idQuestion, title, order, type) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/CheckBoxAnswer.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/CheckBoxAnswer.kt new file mode 100644 index 0000000..c2e2d07 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/CheckBoxAnswer.kt @@ -0,0 +1,11 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class CheckBoxAnswer ( + @SerializedName("pregunta_id") + var questionId: String, + + @SerializedName("respuesta_id") + var answerId: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/EvidenceRequest.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/EvidenceRequest.kt new file mode 100644 index 0000000..00313eb --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/EvidenceRequest.kt @@ -0,0 +1,18 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class EvidenceRequest( + + @SerializedName("nombre") + var name: String, + + @SerializedName("etapa") + var stage: String, + + @SerializedName("lat") + var lat: String, + + @SerializedName("lng") + var lng: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/EvidenceSignatureLocal.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/EvidenceSignatureLocal.kt new file mode 100644 index 0000000..6de8730 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/EvidenceSignatureLocal.kt @@ -0,0 +1,6 @@ +package com.iesoluciones.siodrenax.models + +data class EvidenceSignatureLocal( + var path: String?, + var name: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/FinishOrderRequest.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/FinishOrderRequest.kt new file mode 100644 index 0000000..8686746 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/FinishOrderRequest.kt @@ -0,0 +1,44 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName +import javax.annotation.Nullable + +data class FinishOrderRequest( + + @SerializedName("servicio_det_id") + var idOrder: String, + + @SerializedName("servicio_enc_id") + var idRequest: String, + + @SerializedName("fecha_fin_celular") + var endDate: String, + + @SerializedName("lat_fin") + var endLat: String, + + @SerializedName("lng_fin") + var endLng: String, + + @SerializedName("duracion") + var elapsedTime: String, + + @SerializedName("comentarios") + var comments: String, + + @SerializedName("aplica_garantia") + var warranty: Int, + + @SerializedName("litraje") + var liters: Int, + + @SerializedName("cat_motivos_estatus_id") + var negativeService: Int, + + @Nullable + @SerializedName("encuesta") + var survey: List?, + + @SerializedName("recibo") + var receipt: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/GenericResponse.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/GenericResponse.kt new file mode 100644 index 0000000..547c39a --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/GenericResponse.kt @@ -0,0 +1,8 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class GenericResponse ( + @SerializedName("result") + internal var response: String +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/KeyValue.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/KeyValue.kt new file mode 100644 index 0000000..8741f98 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/KeyValue.kt @@ -0,0 +1,18 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class KeyValue ( + @SerializedName("llave") + var key: String, + + @SerializedName("valor") + var value: String +){ + override fun toString(): String { + return "KeyValue{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/LoginResponse.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/LoginResponse.kt new file mode 100644 index 0000000..f59a1d5 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/LoginResponse.kt @@ -0,0 +1,23 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.entities.User + +data class LoginResponse ( + @SerializedName("user") + internal var user: User, + + @SerializedName("token") + internal var token: String, + + @SerializedName("parametros") + internal var parmams: List, +) { + override fun toString(): String { + return "LoginResponse{" + + "user=" + user + + ", token='" + token + '\'' + + ", parmams=" + parmams + + '}' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/OpenAnswer.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/OpenAnswer.kt new file mode 100644 index 0000000..cac4284 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/OpenAnswer.kt @@ -0,0 +1,11 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class OpenAnswer ( + @SerializedName("pregunta_id") + var questionId: String, + + @SerializedName("respuesta") + var answer: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/OrderPdf.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/OrderPdf.kt new file mode 100644 index 0000000..ba6a39a --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/OrderPdf.kt @@ -0,0 +1,25 @@ +package com.iesoluciones.siodrenax.models + +data class OrderPdf( + var requestId: Long, + var client: String, + var date: String, + var address: String, + var city: String, + var cellphone: String, + var contactName: String, + var service: String, + var observations: String, + var warrantyApplied: String, + var cost: String, + var negativeCost: String, + var startTime: String, + var elapsedTime: String, + var finishTime: String, + var advisor: String, + var branchOffice: String, + var sign: String, + var auxiliar: String, + var vehicle: String, + var evidences: MutableMap> +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/Question.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/Question.kt new file mode 100644 index 0000000..2571ad9 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/Question.kt @@ -0,0 +1,9 @@ +package com.iesoluciones.siodrenax.models + +abstract class Question { + abstract fun getId(): Long + abstract fun getTitle(): String + abstract fun getOrder(): Int + abstract fun getShowNumber(): Int + abstract fun getRequired(): Boolean +} diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/QuestionPOJO.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/QuestionPOJO.kt new file mode 100644 index 0000000..dada30c --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/QuestionPOJO.kt @@ -0,0 +1,33 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.entities.BusinessQuestion +import com.iesoluciones.siodrenax.entities.DomesticQuestion + +data class QuestionPOJO( + @SerializedName("id") + var id: Long, + + @SerializedName("nombre") + var title: String, + + @SerializedName("orden") + var order: Int, + + @SerializedName("mostrar_numero") + var showNumber: Int, + + @SerializedName("obligatorio") + var required: Int, + + @SerializedName("respuestas") + var answers: List +){ + fun getDomesticQuestion(): DomesticQuestion { + return DomesticQuestion(id, title, order, showNumber, (required == 1)) + } + + fun getBusinessQuestion(): BusinessQuestion { + return BusinessQuestion(id, title, order, showNumber, (required == 1)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/Reason.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/Reason.kt new file mode 100644 index 0000000..1ab3bf0 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/Reason.kt @@ -0,0 +1,6 @@ +package com.iesoluciones.siodrenax.models + +abstract class Reason { + abstract fun getId(): Long + abstract fun getTitle(): String +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/StartWorkdayResponse.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/StartWorkdayResponse.kt new file mode 100644 index 0000000..62f00f7 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/StartWorkdayResponse.kt @@ -0,0 +1,47 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName +import com.iesoluciones.siodrenax.entities.BusinessQuestion +import com.iesoluciones.siodrenax.entities.DomesticQuestion +import com.iesoluciones.siodrenax.entities.NegativeServiceReason +import com.iesoluciones.siodrenax.entities.Order +import java.util.* + +data class StartWorkdayResponse( + + @SerializedName("servicios") + var orders: List, + + @SerializedName("parametros") + var keyValues: List, + + @SerializedName("jornada") + var workdayInfo: Workday, + + @SerializedName("encuesta_domestico") + var domesticSurvey: List, + + @SerializedName("encuesta_empresarial") + var businessSurvey: List, + + @SerializedName("motivos_estatus") + var negativeServiceReason: List +){ + fun parseDomesticSurvey(): List { + val survey: MutableList = ArrayList() + for (q in domesticSurvey) { + val question: DomesticQuestion = q.getDomesticQuestion() + survey.add(question) + } + return survey + } + + fun parseBussinesSurvey(): List { + val survey: MutableList = ArrayList() + for (q in businessSurvey) { + val question: BusinessQuestion = q.getBusinessQuestion() + survey.add(question) + } + return survey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/ValidationOrderAnswer.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/ValidationOrderAnswer.kt new file mode 100644 index 0000000..cfcb41c --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/ValidationOrderAnswer.kt @@ -0,0 +1,5 @@ +package com.iesoluciones.siodrenax.models + +data class ValidationOrderAnswer( + var servicio_id: String +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/VehicleIncidenceResponse.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/VehicleIncidenceResponse.kt new file mode 100644 index 0000000..0de7270 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/VehicleIncidenceResponse.kt @@ -0,0 +1,18 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class VehicleIncidenceResponse ( + @SerializedName("id") + var id: Int, + + @SerializedName("descripcion") + var description: String +){ + override fun toString(): String{ + return "Incidence {" + + "id =" + id + + ", descripcion = " + description + + '}' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/models/Workday.kt b/app/src/main/java/com/iesoluciones/siodrenax/models/Workday.kt new file mode 100644 index 0000000..44ccc29 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/models/Workday.kt @@ -0,0 +1,26 @@ +package com.iesoluciones.siodrenax.models + +import com.google.gson.annotations.SerializedName + +data class Workday ( + @SerializedName("kilometraje_inicial") + var startOdometer: String, + + @SerializedName("lat_ini") + var initLat: String, + + @SerializedName("lng_ini") + var initLng: String, + + @SerializedName("vehiculo_id") + var idVehicle: Long, + + @SerializedName("usuario_id") + var idUser: Long, + + @SerializedName("fecha_hora_ini") + var initDate: String, + + @SerializedName("id") + var idWorkload: Long, +) \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/network/Api.kt b/app/src/main/java/com/iesoluciones/siodrenax/network/Api.kt new file mode 100644 index 0000000..15f014b --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/network/Api.kt @@ -0,0 +1,118 @@ +package com.iesoluciones.siodrenax.network + +import com.iesoluciones.siodrenax.entities.* +import com.iesoluciones.siodrenax.models.* +import io.reactivex.Observable +import okhttp3.ResponseBody +import retrofit2.http.* +import java.util.* + +interface Api { + + @FormUrlEncoded + @POST("login") + fun login( + @Header("Application") appId: String, + @Field("email") email: String, + @Field("password") password: String, + @Field("version_apk") version: String, + @Field("dispositivo_id") deviceId: String + ): Observable + + @GET("operador/checklist") + fun getCheckList( + @Query("cambiar_vehiculo") changeVehicle: Int + ): Observable> + + @GET("operador/checklist/vehiculos") + fun getVehicle(): Observable> + + @FormUrlEncoded + @POST("operador/vehiculos_incidencias") + fun sendVehicleIncidence( + @Field("descripcion") incidence: String? + ): Observable + + @FormUrlEncoded + @POST("operador/vehiculos_incidencias/ultima_incidencia") + fun getVehicleIncidence( + @Field("vehiculo_id") vehicleId: Long + ): Observable + + @PUT("operador/vehiculos_incidencias/{incidencia_id}/resolver") + fun resolveVehicleIncidence( + @Path("incidencia_id") incidenceId: String + ): Observable + + @POST("operador/checklist") + fun sendCheckList( + @Body checkListQuestions: List + ): Observable + + @FormUrlEncoded + @POST("operador/iniciarjornada") + fun startWorkday( + @Field("token_firebase") firebaseToken: String, + @Field("kilometraje_inicial") odometer: String, + @Field("lat_ini") initLat: String, + @Field("lng_ini") initLng: String, + @Field("modelo_celular") phoneModel: String, + @Field("bateria") batteryLevel: String + ): Observable + + @FormUrlEncoded + @POST("operador/finalizarjornada/{bitacora_id}") + fun endWorkday( + @Path("bitacora_id") idWorkday: String, + @Field("kilometraje_final") odometer: String, + @Field("lat_fin") lastLat: String, + @Field("lng_fin") lastLng: String, + @Field("modelo_celular") phoneModel: String, + @Field("bateria") batteryLevel: String + ): Observable + + @GET("operador/solicitud_servicios") + fun getOrders(): Observable> + + @GET("operador/solicitud_servicios/{idOrder}") + fun getOrderInfo( + @Path("idOrder") idWorkday: String + ): Observable + + @FormUrlEncoded + @POST("operador/solicitud_servicios/iniciar") + fun startOrder( + @Field("servicio_det_id") idOrder: String, + @Field("servicio_enc_id") idRequest: String, + @Field("fecha_ini_celular") startDate: String, + @Field("lat_ini") startLat: String, + @Field("lng_ini") startLng: String + ): Observable + + @POST("operador/solicitud_servicios/verificar/servicios") + fun verificarServicio(@Body servicios_id: ArrayList): Observable> + + @POST("operador/solicitud_servicios/finalizar-new") + fun finishOrder( + @Body finishOrderRequest: FinishOrderRequest, + ): Observable + + @GET("supervisoroperaciones/asesores") + fun listOperators(): Observable> + + @GET("supervisoroperaciones/asesores/{asesor_id}/servicios") + fun getOperatorOrders( + @Path("asesor_id") idOperator: String + ): Observable> + + @POST("supervisoroperaciones/iniciarjornada") + fun startWorkdayManager(): Observable + + @POST("supervisoroperaciones/finalizarjornada/{bitacora_id}") + fun endWorkdayManager( + @Path("bitacora_id") workdayId: String + ): Observable + + @GET("operador/servicios/diasiguiente") + fun getNextDayOrders(): Observable> +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/network/HttpError.kt b/app/src/main/java/com/iesoluciones/siodrenax/network/HttpError.kt new file mode 100644 index 0000000..d5ebde0 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/network/HttpError.kt @@ -0,0 +1,35 @@ +package com.iesoluciones.siodrenax.network + +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import retrofit2.HttpException +import java.io.IOException + +internal class HttpError(json: String) { + + @SerializedName("error") + var error: String? = null + + init { + val gson = Gson() + val temp = gson.fromJson(json, HttpError::class.java) + error = temp.error + } + + companion object { + + fun parseException(exception: HttpException): HttpError? { + return try { + HttpError(exception.response().errorBody()!!.source().readUtf8()) + } catch (e: IOException) { + e.printStackTrace() + null + } + } + } + + override fun toString(): String { + return "{error : $error}" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/network/TokenInterceptor.kt b/app/src/main/java/com/iesoluciones/siodrenax/network/TokenInterceptor.kt new file mode 100644 index 0000000..56c7d29 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/network/TokenInterceptor.kt @@ -0,0 +1,166 @@ +package com.iesoluciones.siodrenax.network + +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.BuildConfig.BASE_URL +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.userBox +import okhttp3.* +import okhttp3.logging.HttpLoggingInterceptor +import okio.Buffer +import org.json.JSONException +import org.json.JSONObject +import java.io.IOException +import java.util.concurrent.TimeUnit + +class TokenInterceptor : Interceptor { + + private var client: OkHttpClient? = null + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val original = chain.request() + val builder = original.newBuilder() + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Bearer " + preferencesHelper.tokenApi) + + val request = builder.build() + val response: Response = chain.proceed(request) + + //Instantiate Logging interceptor + val logging = HttpLoggingInterceptor() + logging.level = HttpLoggingInterceptor.Level.BODY + + //Let's finish last workday started before starting a new one + //Instantiate Http client to use + client = OkHttpClient.Builder() + .addInterceptor(TokenInterceptor()) + .addInterceptor(logging) + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .build() + + when (response.code()) { + 401 -> { + val body1 = response.body()!!.source().readUtf8() + val error = HttpError(body1) + + if (error.error != null) { + if (error.error.toString().equals( + App.shareInstance!!.resources.getString(R.string.token_expired), + true + ) + ) { + val request1 = Request.Builder() + .url(BASE_URL + "refresh") + .build() + val response1 = client!!.newCall(request1).execute() + try { + val json = JSONObject(response1.body()!!.source().readUtf8()) + preferencesHelper.tokenApi = json.getString("token") + val original1 = chain.request() + val builder1 = original1.newBuilder() + val request2 = builder1.build() + return client!!.newCall(request2).execute() + } catch (e: JSONException) { + e.printStackTrace() + } + } else + return response.newBuilder() + .body(ResponseBody.create(response.body()!!.contentType(), body1)) + .code(401).build() + } else + return response.newBuilder() + .body(ResponseBody.create(response.body()!!.contentType(), body1)) + .code(401).build() + } + 420 -> { + val body1 = response.body()!!.source().readUtf8() + + return response.newBuilder() + .body(ResponseBody.create(response.body()!!.contentType(), body1)) + .code(420).build() + } + 422 -> { + val body1 = response.body()!!.source().readUtf8() + val error1 = UnprocessableEntity(body1) + + if (error1.message.contentEquals(App.shareInstance!!.resources.getString(R.string.workday_already_started))) { + + //Extract the request body, the same data of the body will be used to finish pending workday + var requestBody = request.body() + + //Modify some keys to match the one requested by the web service + requestBody = processFormDataRequestBody(requestBody!!) + val response1: Response + + if (userBox.query().build().findUnique()!!.idEmployeeType == 2L) { + //Set up request + val request1 = Request.Builder() + .url( + BASE_URL + "operador/finalizarjornada/" + error1.errors[0] + ) //concatenate id of last workday + .post(requestBody!!) + .build() + //Call response + response1 = client!!.newCall(request1).execute() + } else { + //Set up request + val request1 = Request.Builder() + .url( + BASE_URL + "supervisoroperaciones/finalizarjornada/" + error1.errors[0] + ) //concatenate id of last workday + .post(requestBody!!) + .build() + //Call response + response1 = client!!.newCall(request1).execute() + } + + //Validate succesful workday finish + return if (response1.code() == 200) { + //try again startworkday + client!!.newCall(chain.request()).execute() + } else { + //There was something wrong with the process , just pass the error to main Thread + val body = response1.body()!!.source().readUtf8() + response.newBuilder() + .body(ResponseBody.create(response.body()!!.contentType(), body)) + .code(422).build() + } + + } else { + return response.newBuilder() + .body(ResponseBody.create(response.body()!!.contentType(), body1)) + .code(422).build() + } + } + else -> return response + } + return response + } + + private fun bodyToString(request: RequestBody?): String { + try { + val buffer = Buffer() + if (request != null) + request.writeTo(buffer) + else + return "" + return buffer.readUtf8() + } catch (e: IOException) { + return "did not work" + } + } + + private fun processFormDataRequestBody(requestBody: RequestBody): RequestBody? { + val formBody: RequestBody = FormBody.Builder().build() + var postBodyString = bodyToString(requestBody) + postBodyString += (if (postBodyString.isNotEmpty()) "&" else "") + bodyToString(formBody) + postBodyString = postBodyString.replace("kilometraje_inicial", "kilometraje_final") + postBodyString = postBodyString.replace("lat_ini", "lat_fin") + postBodyString = postBodyString.replace("lng_ini", "lng_fin") + return RequestBody.create(requestBody.contentType(), postBodyString) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/network/UnprocessableEntity.kt b/app/src/main/java/com/iesoluciones/siodrenax/network/UnprocessableEntity.kt new file mode 100644 index 0000000..d5fe464 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/network/UnprocessableEntity.kt @@ -0,0 +1,43 @@ +package com.iesoluciones.siodrenax.network + +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import retrofit2.HttpException +import java.io.IOException + +class UnprocessableEntity(json: String) { + + @SerializedName("message") + var message: String + internal set + @SerializedName("errors") + var errors: List + internal set + + init { + val gson = Gson() + val temp = gson.fromJson(json, UnprocessableEntity::class.java) + message = temp.message + errors = temp.errors + } + + companion object { + + fun parseException(exception: HttpException): UnprocessableEntity? { + return try { + UnprocessableEntity(exception.response().errorBody()!!.source().readUtf8().toString()) + } catch (e: IOException) { + e.printStackTrace() + null + } + } + } + + override fun toString(): String { + return "UnprocessableEntity{" + + "message='" + message + '\''.toString() + + ", errors=" + errors + + '}'.toString() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/receivers/UpdateUIReceiver.kt b/app/src/main/java/com/iesoluciones/siodrenax/receivers/UpdateUIReceiver.kt new file mode 100644 index 0000000..26b0bf9 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/receivers/UpdateUIReceiver.kt @@ -0,0 +1,34 @@ +package com.iesoluciones.siodrenax.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import com.iesoluciones.siodrenax.services.NotificationService.Companion.UPDATE_UI + +class UpdateUIReceiver: BroadcastReceiver() { + + private var listener: UiUpdateListener? = null + + interface UiUpdateListener { + fun updateUI() + } + + fun updateUIReceiver(listener: UiUpdateListener) { + this.listener = listener + (listener as Context).registerReceiver(this, IntentFilter(UPDATE_UI)) + } + + fun unRegisterReceiver(listener: UiUpdateListener) { + (listener as Context).unregisterReceiver(this) + } + + fun registerReceiver(listener: UiUpdateListener) { + this.listener = listener + (listener as Context).registerReceiver(this, IntentFilter(UPDATE_UI)) + } + + override fun onReceive(context: Context?, intent: Intent?) { + if (listener != null) listener!!.updateUI() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/CheckListRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/CheckListRepository.kt new file mode 100644 index 0000000..61b2a7f --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/CheckListRepository.kt @@ -0,0 +1,76 @@ +package com.iesoluciones.siodrenax.repositories + +import com.iesoluciones.siodrenax.api +import com.iesoluciones.siodrenax.checkListQuestionBox +import com.iesoluciones.siodrenax.entities.CheckListQuestion +import com.iesoluciones.siodrenax.entities.CheckListQuestion_ +import com.iesoluciones.siodrenax.utils.Constants +import io.objectbox.kotlin.and +import io.objectbox.kotlin.equal +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody + +class CheckListRepository { + + interface CheckListInterface { + fun success() + fun failure(throwable: Throwable) + } + + fun isCheckListDone(): Boolean { + return checkListQuestionBox.query() + .build() + .find().isNotEmpty() + } + + fun getSurveyCheckList(tipo: String): List { + + return checkListQuestionBox.query( + CheckListQuestion_.tipo equal tipo + ) + .build() + .find() + } + + fun isValidRevision(): Boolean { + val totalRadio = checkListQuestionBox.query( + CheckListQuestion_.tipo equal Constants.REVISION + and (CheckListQuestion_.tipoRadioBtn equal 1) + and (CheckListQuestion_.respuestaRadioBtn.isNull) + ) + .build() + .count() + + val totalSelect = checkListQuestionBox.query( + CheckListQuestion_.tipo equal Constants.REVISION + and (CheckListQuestion_.tipoCheckBox equal 0) + and (CheckListQuestion_.tipoRadioBtn equal 0) + and (CheckListQuestion_.tipoText equal 0) + and (CheckListQuestion_.respuestaText.isNull) + ) + .build() + .count() + + return (totalRadio + totalSelect) == 0L + } + + fun finalizarEncuesta(checkListInterface: CheckListInterface) { + + api.sendCheckList(checkListQuestionBox.all) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + checkListQuestionBox.removeAll() + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(responseBody: ResponseBody) {} + + override fun onError(e: Throwable) = checkListInterface.failure(e) + + override fun onComplete() = checkListInterface.success() + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/EvidenceRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/EvidenceRepository.kt new file mode 100644 index 0000000..cc3cf6d --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/EvidenceRepository.kt @@ -0,0 +1,15 @@ +package com.iesoluciones.siodrenax.repositories + +import com.iesoluciones.siodrenax.entities.Evidence +import com.iesoluciones.siodrenax.evidenceBox + +class EvidenceRepository { + + fun getEvidenceById(idEvidence: Long): Evidence { + return evidenceBox.get(idEvidence) + } + + fun saveEvidence(evidence: Evidence) { + evidenceBox.put(evidence) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/IncidenceRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/IncidenceRepository.kt new file mode 100644 index 0000000..6003ab5 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/IncidenceRepository.kt @@ -0,0 +1,91 @@ +package com.iesoluciones.siodrenax.repositories + +import com.iesoluciones.siodrenax.api +import com.iesoluciones.siodrenax.models.GenericResponse +import com.iesoluciones.siodrenax.models.VehicleIncidenceResponse +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers + +class IncidenceRepository { + + interface IncidenceInterface { + fun success(genericResponse: String?) + fun failure(throwable: Throwable) + } + + interface GetIncidenceInterface { + fun success(response: VehicleIncidenceResponse?) + fun failure(throwable: Throwable) + } + + fun sendVehicleIncidenceRequest(incidence: String, incidenceInterface: IncidenceInterface) { + + api.sendVehicleIncidence(incidence) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(s: GenericResponse) { + incidenceInterface.success(s.response) + } + + override fun onError(e: Throwable) { + incidenceInterface.failure(e) + } + + override fun onComplete() { + + } + }) + } + + fun getVehicleIncidenceRequest(vehicleId: Long, incidenceInterface: GetIncidenceInterface) { + + api.getVehicleIncidence(vehicleId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(s: VehicleIncidenceResponse) { + incidenceInterface.success(s) + } + + override fun onError(e: Throwable) { + incidenceInterface.failure(e) + } + + override fun onComplete() { + + } + }) + } + + fun resolveVehicleIncidenceRequest(incidenceId: String, incidenceInterface: IncidenceInterface) { + + api.resolveVehicleIncidence(incidenceId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(s: GenericResponse) { + incidenceInterface.success(s.response) + } + + override fun onError(e: Throwable) { + incidenceInterface.failure(e) + } + + override fun onComplete() { + + } + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/LoginRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/LoginRepository.kt new file mode 100644 index 0000000..87b4dc3 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/LoginRepository.kt @@ -0,0 +1,62 @@ +package com.iesoluciones.siodrenax.repositories + +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.utils.Constants.Companion.APP_ID +import com.iesoluciones.siodrenax.utils.Constants.Companion.EMPLOYEE_MANAGER +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers + +class LoginRepository { + + interface LoginInterface { + fun success(result: Boolean) + fun failure(throwable: Throwable) + } + + fun loginRequest(username: String, password: String, deviceId: String, loginInterface: LoginInterface) { + + val obLogin = api.login(APP_ID, username, password, BuildConfig.VERSION_NAME, deviceId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + preferencesHelper.tokenApi = response.token + val user = response.user + userBox.put(user) + preferencesHelper.isManager = user.idEmployeeType == EMPLOYEE_MANAGER + response + } + + val obCheckList = api.getCheckList(0) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + checkListQuestionBox.put(response) + response + } + + val obVehicle = api.getVehicle() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + vehicleBox.put(response) + response + } + + Observable.concat(obLogin, obCheckList, obVehicle) + .subscribe(object : ResourceObserver() { + override fun onNext(t: Any) { + } + + override fun onError(e: Throwable) { + loginInterface.failure(e) + } + + override fun onComplete() { + loginInterface.success(true) + } + }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/ManagerRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/ManagerRepository.kt new file mode 100644 index 0000000..d062aa9 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/ManagerRepository.kt @@ -0,0 +1,121 @@ +package com.iesoluciones.siodrenax.repositories + +import com.iesoluciones.siodrenax.api +import com.iesoluciones.siodrenax.entities.Operator +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.entities.User +import com.iesoluciones.siodrenax.models.Workday +import com.iesoluciones.siodrenax.operatorBox +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.userBox +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody + +class ManagerRepository { + + interface OnOrdersRetrievedListener { + fun success(orders: List) + fun failure(throwable: Throwable) + } + + interface OnOperatorsRetrievedListener { + fun success(operators: List) + fun failure(throwable: Throwable) + } + + interface OnWebServiceCalled { + fun success() + fun failure(e: Throwable) + } + + fun getOperatorList(listener: OnOperatorsRetrievedListener){ + api.listOperators() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver>() { + override fun onNext(operators: List) { + operatorBox.put(operators) + listener.success(operators) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + fun getOperatorOrders(idOperator: String, listener: OnOrdersRetrievedListener){ + api.getOperatorOrders(idOperator) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver>() { + override fun onNext(orders: List) { + listener.success(orders) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + fun getOperatorsFromDb(): List { + return operatorBox.all + } + + fun logOut(){ + operatorBox.removeAll() + userBox.removeAll() + preferencesHelper.tokenApi = null + preferencesHelper.tokenFirebase = null + preferencesHelper.isManager = false + preferencesHelper.workdayStarted = false + preferencesHelper.workdayId = 0 + } + + fun startWorkday(listener: OnWebServiceCalled){ + api.startWorkdayManager() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver() { + override fun onNext(workday: Workday) { + preferencesHelper.workdayId = workday.idWorkload.toInt() + listener.success() + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + fun endWorkday(listener: OnWebServiceCalled){ + api.endWorkdayManager(preferencesHelper.workdayId.toString()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver() { + override fun onNext(responseBody: ResponseBody) { + logOut() + listener.success() + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + fun getUserData(): User { + return userBox.all[0] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/OrdersRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/OrdersRepository.kt new file mode 100644 index 0000000..0b00050 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/OrdersRepository.kt @@ -0,0 +1,349 @@ +package com.iesoluciones.siodrenax.repositories + +import android.content.Intent +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.App.Companion.shareInstance +import com.iesoluciones.siodrenax.entities.* +import com.iesoluciones.siodrenax.models.EvidenceRequest +import com.iesoluciones.siodrenax.models.FinishOrderRequest +import com.iesoluciones.siodrenax.models.Question +import com.iesoluciones.siodrenax.models.ValidationOrderAnswer +import com.iesoluciones.siodrenax.services.NotificationService +import com.iesoluciones.siodrenax.utils.Constants +import com.iesoluciones.siodrenax.utils.HelperUtil +import io.objectbox.kotlin.equal +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody + + +class OrdersRepository { + + interface OnRefreshOrders { + fun success(orders: List) + fun failure(throwable: Throwable) + } + + interface OnRefreshONextDayOrders { + fun success(nextDayOrders: List) + fun failure(throwable: Throwable) + } + + interface OnOrderRequestAnswered { + fun success(orderResponse: ResponseBody) + fun failure(throwable: Throwable) + } + + fun getOrders(): List { + return orderBox.query() + .order(Order_.dateServiceRequest) + .build() + .find() + } + + fun getOrderById(id: Long): Order? { + return orderBox.query() + .equal(Order_.id, id) + .build() + .findFirst() + } + + fun getOrderByIdRequest(idRequest: Long): Order? { + return orderBox.query() + .equal(Order_.idRequest, idRequest) + .build() + .findFirst() + } + + fun getOrderProgressById(id: Long): OrderProgress? { + return orderProgressBox.query() + .equal(OrderProgress_.id, id) + .build() + .findFirst() + } + + fun refreshOrders(listener: OnRefreshOrders, filesDir: String) { + + api.getOrders() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver>() { + override fun onNext(orders: List) { + + val ordersInBox: List = + orderBox.query().order(Order_.dateServiceRequest).build().find() + for (o in ordersInBox) { + val orderProgress: OrderProgress? = getOrderProgressById(o.id) + if (orderProgress == null || orderProgress.isSent) { + orderBox.remove(o.id) + } + } + orderBox.put(orders) + HelperUtil().downloadSketchs(filesDir,orders) + listener.success(getOrders()) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + fun startOrder( + idOrder: String, + idRequest: String, + startDate: String, + startLat: String, + startLng: String, + listener: OnOrderRequestAnswered + ) { + api.startOrder(idOrder, idRequest, startDate, startLat, startLng) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver() { + override fun onNext(responseBody: ResponseBody) { + HelperUtil().createEvidences(getOrderById(idOrder.toLong())!!) + val orderProgress = OrderProgress() + orderProgress.id = idOrder.toLong() + orderProgress.idRequest = idRequest.toLong() + orderProgress.startDate = startDate + orderProgress.startLat = startLat + orderProgress.startLng = startLng + + orderProgressBox.put(orderProgress) + preferencesHelper.orderInProgress = idOrder.toLong() + listener.success(responseBody) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + fun createSurveyAnswerHolders(order: Order) { + val surveyRepository = SurveyRepository() + + val questions: List = + if (order.idServiceType == Constants.ID_SERVICE_TYPE_DOMESTIC) { + surveyRepository.getSurveyQuestions(true) + } else { + surveyRepository.getSurveyQuestions(false) + } + + for (q in questions) { + val savedAnswer = SavedAnswer() + savedAnswer.orderId = order.id + savedAnswer.questionId = q.getId() + + savedAnswerBox.put(savedAnswer) + } + } + + fun getEvidences(idOrder: Long): List { + return evidenceBox.query() + .equal(Evidence_.idOrder, idOrder) + .order(Evidence_.id) + .build() + .find(0, 9) + } + + fun getEvidencesForZip(idOrder: Long): List { + return evidenceBox.query() + .equal(Evidence_.idOrder, idOrder) + .and() + .notNull(Evidence_.path) + .build() + .find() + } + + fun getEvidencesForPdf(idOrder: Long): List { + return evidenceBox.query() + .equal(Evidence_.idOrder, idOrder) + .and() + .notNull(Evidence_.path) + .order(Evidence_.evidenceNo) + .build() + .find() + } + + fun saveOrderProgress(orderProgress: OrderProgress) { + orderProgressBox.put(orderProgress) + } + + fun getPendingOrders(): List { + //System.gc() + + val ordersIdList = orderProgressBox.query() + .equal(OrderProgress_.shouldBeSent, true) + .and().equal(OrderProgress_.isSent, false) + .build() + .findIds() + + validateOrdersSuccess(ordersIdList.toCollection(ArrayList())) + + return orderProgressBox.query() + .equal(OrderProgress_.shouldBeSent, true) + .and().equal(OrderProgress_.isSent, false) + .build() + .find() + } + + private fun validateOrdersSuccess(ordersIdList: ArrayList) { + api.verificarServicio(ordersIdList) + .subscribeOn(Schedulers.trampoline()) + .observeOn(Schedulers.trampoline()) + .subscribe(object : ResourceObserver>() { + override fun onNext(validationOrderAnswers: List) { + for (v in validationOrderAnswers) { + + evidenceBox.query( + Evidence_.idOrder equal v.servicio_id + ) + .build() + .remove() + + orderProgressBox.remove(v.servicio_id.toLong()) + orderBox.remove(v.servicio_id.toLong()) + + //TODO + // shareInstance!!.sendBroadcast(Intent(PushNotificationMessagingService.UPDATE_UI)) + // shareInstance!!.startService(Intent(shareInstance, CleaningService::class.java)) + preferencesHelper.orderInProgress = null + } + } + + override fun onError(e: Throwable) {} + override fun onComplete() {} + }) + } + + private fun getPendingEvidences(idOrder: String): List { + return evidenceBox.query() + .equal(Evidence_.idOrder, idOrder.toLong()) + .and().equal(Evidence_.isSent, false) + .and().notNull(Evidence_.path) + .build() + .find() + } + + fun prepareFinishOrder(orderProgress: OrderProgress): Observable { + + val order = getOrderById(orderProgress.id)!! + val answers: List = SurveyRepository().getSurveyBody(order) + + val evidences = arrayListOf() + getEvidencesForZip(orderProgress.id).forEach { + evidences.add(EvidenceRequest(it.name!!, it.getTypeDescription(), it.lat, it.lng)) + } + + val finishOrderRequest = FinishOrderRequest( + idOrder = orderProgress.id.toString(), + idRequest = orderProgress.idRequest.toString(), + endDate = orderProgress.endDate!!, + endLat = orderProgress.endLat, + endLng = orderProgress.endLng, + elapsedTime = orderProgress.elapsedTime!!, + comments = orderProgress.comments!!, + warranty = if (orderProgress.warranty) 1 else 0, + liters = orderProgress.liters, + negativeService = orderProgress.negativeService, + survey = if (order.SurveyRequired == 1 && orderProgress.shouldSendSurvey) answers else null, + receipt = HelperUtil().encodeBase64(order.pdfPath!!)!! + ) + + return api.finishOrder(finishOrderRequest) + .doOnNext { + removeAllOrdersInformation( + orderProgress.id, + order.pdfPath!!, + orderProgress.signaturePath!! + ) + shareInstance!!.sendBroadcast(Intent(NotificationService.UPDATE_UI)) + preferencesHelper.orderInProgress = null + } + .doOnError { + orderProgress.isSent = false + orderProgressBox.put(orderProgress) + } + .subscribeOn(Schedulers.newThread()) + .observeOn(AndroidSchedulers.mainThread()) + } + + fun validateEvidences(idOrder: String): Boolean { + // Validar almenos una imagen de cada etapa + /* + val evidences = getPendingEvidences(idOrder) + var hasAtLeastOneStartEvicende = false + var hasAtLeastOneProcessEvicende = false + var hasAtLeastOneFinalEvicende = false + + for (e in evidences) { + when (e.type) { + Constants.EVIDENCE_START -> hasAtLeastOneStartEvicende = true + Constants.EVIDENCE_PROCESS -> hasAtLeastOneProcessEvicende = true + Constants.EVIDENCE_FINAL -> hasAtLeastOneFinalEvicende = true + } + } + return hasAtLeastOneStartEvicende && hasAtLeastOneProcessEvicende && hasAtLeastOneFinalEvicende + */ + + return getPendingEvidences(idOrder).size == 9 + } + + fun getNextDayOrders(): List { + return nextDayOrderBox.query() + .order(NextDayOrder_.dateServiceRequest) + .build() + .find() + } + + fun refreshNextDayOrders(listener: OnRefreshONextDayOrders) { + api.getNextDayOrders() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver>() { + override fun onNext(orders: List) { + nextDayOrderBox.removeAll() + nextDayOrderBox.put(orders) + listener.success(getNextDayOrders()) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() {} + }) + } + + private fun removeAllOrdersInformation(idOrder: Long, pdfPath: String, signaturePath: String) { + + getEvidencesForZip(idOrder).forEach { + HelperUtil().deleteFile(it.path!!) + } + + HelperUtil().deleteFile(pdfPath) + HelperUtil().deleteFile(signaturePath) + + evidenceBox.query() + .equal(Evidence_.idOrder, idOrder) + .build() + .remove() + + savedAnswerBox.query() + .equal(SavedAnswer_.orderId, idOrder) + .build() + .remove() + + orderProgressBox.remove(idOrder) + + orderBox.remove(idOrder) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/SurveyRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/SurveyRepository.kt new file mode 100644 index 0000000..b7bf557 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/SurveyRepository.kt @@ -0,0 +1,104 @@ +package com.iesoluciones.siodrenax.repositories + +import android.util.Log +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.entities.* +import com.iesoluciones.siodrenax.models.* +import com.iesoluciones.siodrenax.utils.Constants.Companion.IDSERVICETYPEDOMESTIC +import java.util.* + +class SurveyRepository { + + fun getSurveyQuestions(isDomestic: Boolean): List { + val questions: MutableList = ArrayList() + if (isDomestic) { + questions.addAll(domesticQuestionBox.all) + } else { + questions.addAll(businessQuestionBox.all) + } + return questions + } + + fun getSurveyBody(order: Order): List { + // NullPointerException launched once. The "Order" is a non null object. + // Commented to track the error -------------> Incidents = 12 + val answerKeyValues: MutableList = ArrayList() + val questions = + getSurveyQuestions(order.idServiceType == IDSERVICETYPEDOMESTIC) + for (q in questions) { + val answer: SavedAnswer? = getSavedAnswer(q, order) + if (answer != null) { + if (answer.isOpen) + answerKeyValues.add(OpenAnswer(q.getId().toString(), answer.answer)) + else + answerKeyValues.add(CheckBoxAnswer(q.getId().toString(), answer.answer)) + } + } + return answerKeyValues + } + + fun deleteAllSavedAnswers(order: Order) { + savedAnswerBox.query() + .equal(SavedAnswer_.orderId, order.id) + .build() + .remove() + } + + private fun getSavedAnswer(question: Question, order: Order): SavedAnswer? { + return savedAnswerBox.query() + .equal(SavedAnswer_.orderId, order.id) + .and().equal(SavedAnswer_.questionId, question.getId()) + .build() + .findFirst() + } + + fun getNegativeServiceReason(): List { + return negativeServiceReasonBox.all + } + + fun validateAnswers(order: Order): Boolean { + val questions = getSurveyQuestions(order.idServiceType == IDSERVICETYPEDOMESTIC) + for (q in questions) { + + val answer: String? = getQuestionSavedAnswer(q, order) + if (q.getRequired() && (answer == null || answer.trim { it <= ' ' } == "")) + return false + } + + return true + } + + fun getQuestionSavedAnswer(question: Question, order: Order): String? { + return getSavedAnswer(question, order)?.answer + } + + fun getAnswers(q: Question, isDomestic: Boolean): List { + return if (isDomestic) getDomesticQuestionAnswers(q) else getBusinessQuestionAnswers(q) + } + + private fun getDomesticQuestionAnswers(q: Question): List { + return domesticAnswerBox.query() + .equal(DomesticAnswer_.idQuestion, q.getId()) + .build() + .find() + } + + private fun getBusinessQuestionAnswers(q: Question): List { + return businessAnswerBox.query() + .equal(BusinessAnswer_.idQuestion, q.getId()) + .build() + .find() + } + + fun saveQuestionAnswer(question: Question, order: Order, answer: String?, isOpen: Boolean) { + val savedAnswer = getSavedAnswer(question, order) + + if (savedAnswer != null) { + savedAnswer.isOpen = isOpen + savedAnswer.answer = answer + savedAnswerBox.put(savedAnswer) + } else { + Log.e("SurveyRepository", "Trouble trying to save answer $answer") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/repositories/WorkdayRepository.kt b/app/src/main/java/com/iesoluciones/siodrenax/repositories/WorkdayRepository.kt new file mode 100644 index 0000000..fff44c8 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/repositories/WorkdayRepository.kt @@ -0,0 +1,251 @@ +package com.iesoluciones.siodrenax.repositories + +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager +import android.os.Build +import android.os.Build.VERSION_CODES +import androidx.core.app.NotificationManagerCompat +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.App.Companion.context +import com.iesoluciones.siodrenax.App.Companion.shareInstance +import com.iesoluciones.siodrenax.entities.OrderProgress_ +import com.iesoluciones.siodrenax.entities.User +import com.iesoluciones.siodrenax.models.StartWorkdayResponse +import com.iesoluciones.siodrenax.utils.HelperUtil +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody + +class WorkdayRepository { + + interface WorkdayInterface { + fun success(response: StartWorkdayResponse) + fun failure(throwable: Throwable) + } + + interface WorkdayEndInterface { + fun success(response: ResponseBody) + fun failure(throwable: Throwable) + } + + fun startWorkdayRequest( + firebaseToken: String, + odometer: String, + initLat: String, + initLng: String, + filesDir: String, + listener: WorkdayInterface + ) { + //obtain device model info + val reqString = (Build.MANUFACTURER + + " " + Build.MODEL + " " + Build.VERSION.RELEASE + + " " + VERSION_CODES::class.java.fields[Build.VERSION.SDK_INT].name) + + //Obtain device battery percentage in integer + val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val batteryStatus: Intent = shareInstance!!.registerReceiver(null, ifilter)!! + val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + val batteryPct = level / scale.toDouble() * 100.0 + val battery = batteryPct.toInt() + + api.startWorkday( + firebaseToken, + odometer, + initLat, + initLng, + reqString, + battery.toString() + "" + ) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + processWorkdayResponse(response, filesDir) + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(response: StartWorkdayResponse) { + listener.success(response) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() { + + } + }) + } + + + private fun processWorkdayResponse(response: StartWorkdayResponse, filesDir: String){ + preferencesHelper.workdayStarted = true + preferencesHelper.workdayId = response.workdayInfo.idWorkload.toInt() + + orderBox.put(response.orders) + domesticQuestionBox.put(response.parseDomesticSurvey()) + businessQuestionBox.put(response.parseBussinesSurvey()) + negativeServiceReasonBox.put(response.negativeServiceReason) + + for (q in response.businessSurvey) + for (answerPOJO in q.answers) + businessAnswerBox.put(answerPOJO.getBusinessAnswer()) + + for (q in response.domesticSurvey) + for (answerPOJO in q.answers) + domesticAnswerBox.put(answerPOJO.getDomesticAnswer()) + + + for (keyValue in response.keyValues) + if (keyValue.key == shareInstance!!.getString(R.string.gps_interval_param)) + preferencesHelper.gpsInterval = keyValue.value.toInt() + + + HelperUtil().downloadSketchs(filesDir,response.orders) + } + + fun endWorkdayRequest( + odometer: String, + endLat: String, + endLng: String, + listener: WorkdayEndInterface + ){ + + val reqString = (Build.MANUFACTURER + + " " + Build.MODEL + " " + Build.VERSION.RELEASE + + " " + VERSION_CODES::class.java.fields[Build.VERSION.SDK_INT].name) + + val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val batteryStatus: Intent = shareInstance!!.registerReceiver(null, ifilter)!! + val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + val batteryPct = level / scale.toDouble() * 100.0 + val battery = batteryPct.toInt() + + api.endWorkday( + preferencesHelper.workdayId.toString(), + odometer, + endLat, + endLng, + reqString, + battery.toString() + ) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + preferencesHelper.workdayStarted = false + preferencesHelper.workdayId = 0 + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(response: ResponseBody) { + listener.success(response) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() { + + } + }) + } + + fun validateChangeVehicle(): Boolean { + return orderProgressBox.query() + .equal(OrderProgress_.isSent, false) + .build() + .find() + .isEmpty() + } + + fun changeVehicle( + odometer: String, + endLat: String, + endLng: String, + listener: WorkdayEndInterface + ) { + val reqString = (Build.MANUFACTURER + + " " + Build.MODEL + " " + Build.VERSION.RELEASE + + " " + VERSION_CODES::class.java.fields[Build.VERSION.SDK_INT].name) + + val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + val batteryStatus: Intent = shareInstance!!.registerReceiver(null, ifilter)!! + val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) + val batteryPct = level / scale.toDouble() * 100.0 + val battery = batteryPct.toInt() + + api.endWorkday( + preferencesHelper.workdayId.toString(), + odometer, + endLat, + endLng, + reqString, + battery.toString() + ) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .map { response -> + preferencesHelper.workdayStarted = false + preferencesHelper.workdayId = 0 + response + } + .subscribe(object : ResourceObserver() { + override fun onNext(response: ResponseBody) { + listener.success(response) + } + + override fun onError(e: Throwable) { + listener.failure(e) + } + + override fun onComplete() { + + } + }) + } + + fun getUser(): User { + return userBox.all[0] + } + + fun logout() { + preferencesHelper.tokenApi = null + preferencesHelper.isEnableHerramientaSurvey = true + preferencesHelper.workdayStarted = false + preferencesHelper.orderInProgress = 0L + + userBox.removeAll() + checkListQuestionBox.removeAll() + vehicleBox.removeAll() + orderBox.removeAll() + evidenceBox.removeAll() + businessQuestionBox.removeAll() + domesticQuestionBox.removeAll() + negativeServiceReasonBox.removeAll() + businessAnswerBox.removeAll() + domesticAnswerBox.removeAll() + savedAnswerBox.removeAll() + orderProgressBox.removeAll() + nextDayOrderBox.removeAll() + + NotificationManagerCompat.from(context!!).cancelAll() + } + + fun validateLogOut(): Boolean { + return orderProgressBox.query() + .equal(OrderProgress_.isSent, false) + .build() + .find() + .isEmpty() + } + + fun validateRenewSession() : Boolean { + return orderBox.isEmpty + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/services/NotificationService.kt b/app/src/main/java/com/iesoluciones/siodrenax/services/NotificationService.kt new file mode 100644 index 0000000..8234fcf --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/services/NotificationService.kt @@ -0,0 +1,71 @@ +package com.iesoluciones.siodrenax.services + +import android.content.Intent +import android.os.Handler +import android.os.Looper +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.api +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.orderBox +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.NotificationUtils +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers + +class NotificationService: FirebaseMessagingService() { + + companion object { + const val UPDATE_UI = "update_ui" + } + + override fun onMessageReceived(remoteMessage: RemoteMessage) { + super.onMessageReceived(remoteMessage) + + // ----------------------------------------------------------------------------- + // ---------- Second way to launch notification (valid to foreground) ---------- + + val notification = remoteMessage.data + + if(notification.containsKey("servicio_id")){ + Handler(Looper.getMainLooper()).postDelayed({ + + api.getOrderInfo(notification["servicio_id"]!!) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver() { + override fun onNext(order: Order) { + if (order.id != 0L) { + orderBox.put(order) + + HelperUtil().downloadSketchs(filesDir.absolutePath, mutableListOf(order)) + + sendBroadcast(Intent(UPDATE_UI)) + + NotificationUtils().showNotification( + resources.getString(R.string.app_name), + resources.getString(R.string.notification_accept_succes_message) + ) + } + } + + override fun onError(e: Throwable) { + NotificationUtils().showNotification( + resources.getString(R.string.app_name), + resources.getString(R.string.notification_accept_success_refresh_message) + ) + } + + override fun onComplete() { + } + }) + }, 1000) + } + + + + // ----------------------------------------------------------------------------- + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/CircularProgressButton.java b/app/src/main/java/com/iesoluciones/siodrenax/utils/CircularProgressButton.java new file mode 100644 index 0000000..91eda85 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/CircularProgressButton.java @@ -0,0 +1,206 @@ +package com.iesoluciones.siodrenax.utils; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import androidx.fragment.app.Fragment; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.animation.AccelerateDecelerateInterpolator; + +import com.iesoluciones.siodrenax.R; + + +/** + * Creado por Informática Electoral Proceso Electoral 2017. + * Gustavo@ie-soluciones.com + */ + + +public class CircularProgressButton extends androidx.appcompat.widget.AppCompatButton { + + public interface ProgressListener { + void onProgressStart(); + + void onProgressFinish(); + } + + private final CircularProgressDrawable mInactiveDrawable; + private final CircularProgressDrawable mActiveDrawable; + private CircularProgressDrawable mCurrentDrawable; + private final Vibrator mVibrator; + private ProgressListener mProgressListener; + private Animator currentAnimation; + private boolean mHoldingDown; + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public CircularProgressButton(Context context, AttributeSet attrs) { + super(context, attrs); + setTransformationMethod(null); + if (this.isInEditMode()) { + this.mInactiveDrawable = null; + this.mActiveDrawable = null; + this.mVibrator = null; + return; + } + this.mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + this.mActiveDrawable = this.mCurrentDrawable = new CircularProgressDrawable.Builder() + .setRingWidth(this.getResources().getDimensionPixelOffset(R.dimen.drawable_ring_size)) + .setOutlineColor(this.getResources().getColor(R.color.colorAccent)) + .setCenterColor(this.getResources().getColor(R.color.colorAccent)) + .setRingColor(this.getResources().getColor(R.color.colorPrimaryDark)) + .setFillColor(this.getResources().getColor(R.color.colorPrimaryDark)) + .create(); + this.mInactiveDrawable = new CircularProgressDrawable.Builder() + .setRingWidth(this.getResources().getDimensionPixelOffset(R.dimen.drawable_ring_size)) + .setOutlineColor(this.getResources().getColor(R.color.colorAccent)) + .setCenterColor(this.getResources().getColor(R.color.colorAccent)) + .setRingColor(this.getResources().getColor(R.color.colorPrimaryDark)) + .setFillColor(this.getResources().getColor(R.color.colorPrimaryDark)) + .create(); + this.post(() -> setBackground(mCurrentDrawable)); + } + + @Override + @SuppressWarnings("SuspiciousNameCombination") + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // set height as long as the width so the widget is perfectly circular + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + } + + @Override + @SuppressLint({"ClickableViewAccessibility", "MissingPermission"}) + public boolean onTouchEvent(MotionEvent event) { + if (this.mCurrentDrawable == this.mInactiveDrawable) { + // do no more + return true; + } + if (event.getAction() == MotionEvent.ACTION_DOWN) { + double distance = this.distanceFromCenter(event.getX(), event.getY()); + if (distance < this.getWidth() / 2F) { + this.mVibrator.vibrate(VibrationEffect.createOneShot(25, 10)); + this.mHoldingDown = true; + if (this.mProgressListener != null) { + this.mProgressListener.onProgressStart(); + } + if (this.currentAnimation != null) { + this.currentAnimation.cancel(); + } + this.currentAnimation = prepareStyle2Animation(); + this.currentAnimation.start(); + } + } + if (event.getAction() == MotionEvent.ACTION_UP) { + this.mHoldingDown = false; + if (this.currentAnimation != null) { + this.currentAnimation.cancel(); + } + this.mCurrentDrawable.setProgress(0f); + this.mCurrentDrawable.setCircleScale(0f); + } + if (event.getAction() == MotionEvent.ACTION_MOVE) { + double distance = this.distanceFromCenter(event.getX(), event.getY()); + //if (event.getX() < 0 || event.getY() < 0 || event.getX() > this.getWidth() || event.getY() > this.getHeight()) + if (distance > this.getWidth() / 2F) { + this.mHoldingDown = false; + if (this.currentAnimation != null) { + this.currentAnimation.cancel(); + } + this.mCurrentDrawable.setProgress(0f); + this.mCurrentDrawable.setCircleScale(0f); + } + } + return true; + } + + private double distanceFromCenter(float touchX, float touchY) { + // get centers + int centerX = this.getWidth() / 2; + int centerY = this.getHeight() / 2; + // calculate distances + float distanceX = touchX - centerX; + float distanceY = touchY - centerY; + // pitagora suichi! + return Math.sqrt((distanceX * distanceX) + (distanceY * distanceY)); + } + + /* ownmeth */ + public void setProgressListener(ProgressListener progressListener) { + this.mProgressListener = progressListener; + } + + public ProgressListener getProgressListener() { + return this.mProgressListener; + } + + public void activate() { + this.mCurrentDrawable = this.mActiveDrawable; + this.setBackground(this.mCurrentDrawable); + } + + public void deactivate() { + this.mCurrentDrawable = this.mInactiveDrawable; + this.setBackground(this.mCurrentDrawable); + } + + private Animator prepareStyle2Animation() { + AnimatorSet animation = new AnimatorSet(); + final ObjectAnimator progressAnimation = ObjectAnimator.ofFloat(this.mCurrentDrawable, CircularProgressDrawable.PROGRESS_PROPERTY, 0f, 1f); + progressAnimation.setDuration(750); + progressAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); + Animator innerCircleAnimation = ObjectAnimator.ofFloat(this.mCurrentDrawable, CircularProgressDrawable.CIRCLE_SCALE_PROPERTY, 0f, 1f); + innerCircleAnimation.setDuration(500); + innerCircleAnimation.addListener(new AnimatorListenerAdapter() { + final CircularProgressButton $this = CircularProgressButton.this; + + @SuppressLint("MissingPermission") + @Override + public void onAnimationStart(Animator animation) { + // catch and ignore possible npe's due to ui refreshing + try { + if ($this.mProgressListener instanceof Fragment) { + ((Fragment) $this.mProgressListener).getActivity().getApplicationContext(); + } + if ($this.mProgressListener instanceof Activity) { + ((Activity) $this.mProgressListener).getApplicationContext(); + } + $this.mVibrator.vibrate(VibrationEffect.createOneShot(25, 10)); + } catch (Exception ignore) { + } + } + + @SuppressLint("MissingPermission") + @Override + public void onAnimationEnd(Animator animation) { + // catch and ignore possible npe's due to ui refreshing + try { + if ($this.mProgressListener instanceof Fragment) { + ((Fragment) $this.mProgressListener).getActivity().getApplicationContext(); + } + if ($this.mProgressListener instanceof Activity) { + ((Activity) $this.mProgressListener).getApplicationContext(); + } + if ($this.mHoldingDown) { + $this.mVibrator.vibrate(VibrationEffect.createOneShot(100, 10)); + if ($this.mProgressListener != null) { + $this.mProgressListener.onProgressFinish(); + } + } + } catch (Exception ignore) { + } + } + }); + animation.playSequentially(progressAnimation, innerCircleAnimation); + return animation; + } +} + diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/CircularProgressDrawable.java b/app/src/main/java/com/iesoluciones/siodrenax/utils/CircularProgressDrawable.java new file mode 100644 index 0000000..d405c5a --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/CircularProgressDrawable.java @@ -0,0 +1,419 @@ +package com.iesoluciones.siodrenax.utils; + +/* + * Created by iedeveloper on 14/12/17. + */ + +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +/** + * Circular Progress Drawable. + *

+ * This drawable will produce a circular shape with a ring surrounding it. The ring can appear + * both filled and give a little cue when it is empty. + *

+ * The inner circle size, the progress of the outer ring and if it is loading parameters can be + * controlled, as well the different colors for the three components. + * + * @author Saul Diaz + */ +public class CircularProgressDrawable extends Drawable { + /** + * Factor to convert the factor to paint the arc. + *

+ * In this way the developer can use a more user-friendly [0..1f] progress + */ + public static final int PROGRESS_FACTOR = 360; + /** + * Property Inner Circle Scale. + *

+ * The inner ring is supposed to defaults stay 3/4 radius off the outer ring at (75% scale), but this + * property can make it grow or shrink via this equation: OuterRadius * Scale. + *

+ * A 100% scale will make the inner circle to be the same radius as the outer ring. + */ + public static final String CIRCLE_SCALE_PROPERTY = "circleScale"; + /** + * Property Progress of the outer circle. + *

+ * The progress of the circle. If {@link #setIndeterminate(boolean) indeterminate flag} is set + * to FALSE, this property will be used to indicate the completion of the outer circle [0..1f]. + *

+ * If set to TRUE, the drawable will activate the loading mode, where the drawable will + * show a 90º arc which will be spinning around the outer circle as much as progress goes. + */ + public static final String PROGRESS_PROPERTY = "progress"; + /** + * Property Ring color. + *

+ * Changes the ring filling color + */ + public static final String RING_COLOR_PROPERTY = "ringColor"; + /** + * Property circle color. + *

+ * Changes the inner circle color + */ + public static final String CIRCLE_COLOR_PROPERTY = "circleColor"; + /** + * Property outline color. + *

+ * Changes the outline of the ring color. + */ + public static final String OUTLINE_COLOR_PROPERTY = "outlineColor"; + /** + * Logger Tag for Logging purposes. + */ + public static final String TAG = "CircularProgressDrawable"; + /** + * Paint object to draw the element. + */ + private final Paint paint; + /** + * Ring progress. + */ + protected float progress; + /** + * Color for the empty outer ring. + */ + protected int outlineColor; + /** + * Color for the completed ring. + */ + protected int ringColor; + /** + * Color for the inner circle. + */ + protected int centerColor; + protected final int fillColor; + /** + * Rectangle where the filling ring will be drawn into. + */ + protected final RectF arcElements; + /** + * Width of the filling ring. + */ + protected final int ringWidth; + /** + * Scale of the inner circle. It will affect the inner circle size on this equation: + * ([Biggest length of the Drawable] / 2) - (ringWidth / 2) * scale. + */ + protected float circleScale; + /** + * Set if it is an indeterminate + */ + protected boolean indeterminate; + + /** + * Creates a new CouponDrawable. + * + * @param ringWidth Width of the filled ring + * @param circleScale Scale difference between the outer ring and the inner circle + * @param outlineColor Color for the outline color + * @param ringColor Color for the filled ring + * @param centerColor Color for the center element + */ + CircularProgressDrawable(int ringWidth, float circleScale, int outlineColor, int ringColor, int centerColor, int fillColor) { + this.progress = 0; + this.outlineColor = outlineColor; + this.ringColor = ringColor; + this.centerColor = centerColor; + this.fillColor = fillColor; + this.paint = new Paint(); + this.paint.setAntiAlias(true); + this.ringWidth = ringWidth; + this.arcElements = new RectF(); + this.circleScale = circleScale; + this.indeterminate = false; + } + + @Override + public void draw(Canvas canvas) { + final Rect bounds = getBounds(); + // Calculations on the different components sizes + int size = Math.min(bounds.height(), bounds.width()); + float outerRadius = (size / 2F) - (ringWidth / 2F); + float innerRadius = outerRadius * circleScale; + float offsetX = (bounds.width() - outerRadius * 2) / 2; + float offsetY = (bounds.height() - outerRadius * 2) / 2; + // Outline Circle + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(1); + paint.setColor(outlineColor); + canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius, paint); + // Fill circle + paint.setStyle(Paint.Style.FILL); + paint.setColor(centerColor); + canvas.drawCircle(bounds.centerX(), bounds.centerY(), outerRadius, paint); + // Inner circle + paint.setStyle(Paint.Style.FILL); + paint.setColor(fillColor); + paint.setAlpha(255); + paint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); + canvas.drawCircle(bounds.centerX(), bounds.centerY(), innerRadius, paint); + paint.setMaskFilter(null); + int halfRingWidth = ringWidth / 2; + float arcX0 = offsetX + halfRingWidth; + float arcY0 = offsetY + halfRingWidth; + float arcX = offsetX + outerRadius * 2 - halfRingWidth; + float arcY = offsetY + outerRadius * 2 - halfRingWidth; + // Outer Circle + paint.setColor(ringColor); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(ringWidth); + paint.setStrokeCap(Paint.Cap.ROUND); + arcElements.set(arcX0, arcY0, arcX, arcY); + if (indeterminate) { + canvas.drawArc(arcElements, progress, 90, false, paint); + } else { + canvas.drawArc(arcElements, 360 - 90, progress, false, paint); + } + } + + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + paint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return 1 - paint.getAlpha(); + } + + /** + * Returns the progress of the outer ring. + *

+ * Will output a correct value only when the indeterminate mode is set to FALSE. + * + * @return Progress of the outer ring. + */ + public float getProgress() { + return progress / PROGRESS_FACTOR; + } + + /** + * Sets the progress [0..1f] + * + * @param progress Sets the progress + */ + public void setProgress(float progress) { + if (indeterminate) { + this.progress = progress; + } else { + this.progress = PROGRESS_FACTOR * progress; + } + invalidateSelf(); + } + + /** + * Returns the inner circle scale. + * + * @return Inner circle scale in float multiplier. + */ + public float getCircleScale() { + return circleScale; + } + + /** + * Sets the inner circle scale. + * + * @param circleScale Inner circle scale. + */ + public void setCircleScale(float circleScale) { + this.circleScale = circleScale; + invalidateSelf(); + } + + /** + * Get the indeterminate status of the Drawable + * + * @return TRUE if the Drawable is in indeterminate mode or FALSE if it is in progress mode. + */ + public boolean isIndeterminate() { + return indeterminate; + } + + /** + * Sets the indeterminate parameter. + *

+ * The indeterminate parameter will change the behavior of the Drawable. If the indeterminate + * mode is set to FALSE, the outer ring will be able to be filled by using {@link #setProgress(float) setProgress}. + *

+ * Otherwise the drawable will enter "loading mode" and a 90º arc will be able to be spinned around + * the inner circle. + *

+ * By default, indeterminate mode is set to FALSE. + * + * @param indeterminate TRUE to activate loading mode. FALSE to activate progress mode. + */ + public void setIndeterminate(boolean indeterminate) { + this.indeterminate = indeterminate; + } + + /** + * Gets the outline color. + * + * @return Outline color of the empty ring. + */ + public int getOutlineColor() { + return outlineColor; + } + + /** + * Gets the filled ring color. + * + * @return Returns the filled ring color. + */ + public int getRingColor() { + return ringColor; + } + + /** + * Gets the color of the inner circle. + * + * @return Inner circle color. + */ + public int getCenterColor() { + return centerColor; + } + + /** + * Sets the empty progress outline color. + * + * @param outlineColor Outline color in #AARRGGBB format. + */ + public void setOutlineColor(int outlineColor) { + this.outlineColor = outlineColor; + invalidateSelf(); + } + + /** + * Sets the progress ring color. + * + * @param ringColor Ring color in #AARRGGBB format. + */ + public void setRingColor(int ringColor) { + this.ringColor = ringColor; + invalidateSelf(); + } + + /** + * Sets the inner circle color. + * + * @param centerColor Inner circle color in #AARRGGBB format. + */ + public void setCenterColor(int centerColor) { + this.centerColor = centerColor; + invalidateSelf(); + } + + /** + * Helper class to manage the creation of a CircularProgressDrawable + * + * @author Saul Diaz + */ + public static class Builder { + /** + * Witdh of the stroke of the filled ring + */ + int ringWidth; + /** + * Color of the outline of the empty ring in #AARRGGBB mode. + */ + int outlineColor; + /** + * Color of the filled ring in #AARRGGBB mode. + */ + int ringColor; + /** + * Color of the inner circle in #AARRGGBB mode. + */ + int centerColor; + int fillcolor; + /** + * Scale between the outer ring and the inner circle + */ + float circleScale = 0f; + + /** + * Sets the ring width. + * + * @param ringWidth Default ring width + * @return This builder + */ + public Builder setRingWidth(int ringWidth) { + this.ringWidth = ringWidth; + return this; + } + + /** + * Sets the default empty outer ring outline color. + * + * @param outlineColor Outline color in #AARRGGBB format. + * @return This Builder + */ + public Builder setOutlineColor(int outlineColor) { + this.outlineColor = outlineColor; + return this; + } + + /** + * Sets the progress ring color. + * + * @param ringColor Ring color in #AARRGGBB format. + * @return This Builder + */ + public Builder setRingColor(int ringColor) { + this.ringColor = ringColor; + return this; + } + + /** + * Sets the inner circle color. + * + * @param centerColor Inner circle color in #AARRGGBB format. + * @return This builder + */ + public Builder setCenterColor(int centerColor) { + this.centerColor = centerColor; + return this; + } + + public Builder setFillColor(int fillcolor) { + this.fillcolor = fillcolor; + return this; + } + + /** + * Sets the inner circle scale. Defaults to 0.75. + * + * @param circleScale Inner circle scale. + * @return This builder + */ + public Builder setInnerCircleScale(float circleScale) { + this.circleScale = circleScale; + return this; + } + + /** + * Creates a new CircularProgressDrawable with the requested parameters + * + * @return New CircularProgressDrawableInstance + */ + public com.iesoluciones.siodrenax.utils.CircularProgressDrawable create() { + return new com.iesoluciones.siodrenax.utils.CircularProgressDrawable(ringWidth, circleScale, outlineColor, ringColor, centerColor, fillcolor); + } + } +} + diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/Constants.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/Constants.kt new file mode 100644 index 0000000..86aec90 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/Constants.kt @@ -0,0 +1,98 @@ +package com.iesoluciones.siodrenax.utils + +class Constants { + + companion object { + const val LOGGED_IN = 1 + const val WORKDAY_STARTED=2 + const val NO_SESSION = 3 + const val ORDER_IN_PROGRESS = 4 + const val LOGGED_IN_MANAGER = 5 + const val WORKDAY_STARTED_MANAGER = 6 + + const val APP_ID = "MOVIL" + + const val EMPLOYEE_MANAGER: Long = 6 + + // CheckList + const val REVISION = "REVISION" + const val MATERIAL = "MATERIAL" + const val HERRAMIENTA = "HERRAMIENTA" + const val WORKDAY = "WORKDAY" + + // Evidences + const val EVIDENCE_START = 1 + const val EVIDENCE_PROCESS = 2 + const val EVIDENCE_FINAL = 3 + + // Location + const val DEFAULT_LATITUDE = 24.7726885 + const val DEFAULT_LONGITUDE = -107.4434088 + + // Notifications + const val ORDER_ID = "servicio_id" + + // Survey + const val ID_SERVICE_TYPE_DOMESTIC: Long = 1L + + // Parameters + const val DEFAULT_LITERS: String = "0" + const val HIDE_ALPHA = 0f + const val HIDE_DURATION = 200L + const val SHOW_ALPHA = 1f + const val SHOW_DURATION = 300L + + // OrderDetail + const val EVIDENCE_ID: String = "evidence_id" + const val EVIDENCE_NAME: String = "evidence_name" + const val MILEAGE = "mileage" + const val IDSERVICETYPEDOMESTIC = 1L + const val EVIDENCE_CODE = 2 + const val SIGNATURE_CODE = 10 + const val SURVEY_CODE = 20 + const val REQUEST_PERMISSIONS_REQUEST_CODE = 33 + + // Camera + const val COMPRESS_JPEG_QUALITY_LOW: Int = 15 + const val EVIDENCIA = "Evidencia " + const val SHOW_TITLE_ALPHA = 0f + const val SHOW_TITLE_DURATION = 200L + const val SHOW_TITLE_DELAY = 1000L + + // Survey + const val PDF_URL = "http://drenax.mx/assets/documents/aviso_privacidad.pdf" + const val DRIVE_READER = "https://drive.google.com/viewerng/viewer?embedded=true&url=" + + // Answer Type + const val ANSWER_CHECKBOX = 1 + const val ANSWER_DATE = 2 + const val ANSWER_EMAIL = 3 + const val ANSWER_CURRENCY = 4 + const val ANSWER_NUMBER = 5 + const val ANSWER_TEXT = 6 + + // Notifications + const val EXTRA_NOTIFICATION_CLICKED: String = "notificationClicked" + + // MultipleViewHolderAdapter + const val CERO = "0" + + // Signature + const val ANIMATE_FADE_OUT_ALPHA = 0.0f + const val ANIMATE_FADE_OUT_DURATION = 200L + const val ANIMATE_FADE_IN_DURATION = 200L + const val ANIMATE_FADE_IN_ALPHA = 1.0f + + //Manager + const val OPERATOR_ID: String = "operator_id" + const val OPERATOR_NAME = "operator_name" + + //ZIP + const val BUFFER_SIZE = 6 * 1024 + + //SKETCH + const val BUFFER_SIZE_SKETCH = 1024 * 1024 + const val BRANCH_NAME = "branch_name" + const val SKETCH_PATH = "sketch_path" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/CustomDialogFragment.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/CustomDialogFragment.kt new file mode 100644 index 0000000..93bb668 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/CustomDialogFragment.kt @@ -0,0 +1,128 @@ +package com.iesoluciones.siodrenax.utils + +import android.app.AlertDialog +import android.app.Dialog +import android.app.ProgressDialog +import android.content.Context +import android.view.View +import com.iesoluciones.siodrenax.R + +class CustomDialogFragment(context: Context) { + + private var mContext: Context = context + private lateinit var mDialog: ProgressDialog + private lateinit var mMessage: String + private lateinit var mTitle: String + private lateinit var mSubtitle: String + private lateinit var mTitleCloseButton: String + private var mSelection = 0 + private lateinit var mArrayMessage: Array + private lateinit var mViewType: MessageViewType + private lateinit var mView: View + + enum class MessageViewType { + RADIOBUTTON, DIALOG, ALERTDIALOG, WEBVIEWDIALOG + } + + + fun setTitle(title: String): CustomDialogFragment { + this.mTitle = title + return this + } + + fun messageType(messageViewType: MessageViewType): CustomDialogFragment { + mViewType = messageViewType + return this + } + + fun show() { + when (mViewType) { + MessageViewType.RADIOBUTTON -> showRadioButtonMessage(mTitle, mArrayMessage) + MessageViewType.DIALOG -> showDialog(mTitle, mSubtitle) + MessageViewType.ALERTDIALOG -> showAlertDialogMessage( + mTitle, + mMessage, + mTitleCloseButton + ) + MessageViewType.WEBVIEWDIALOG -> showWebViewDialog(mView) + } + } + + private fun showRadioButtonMessage(titulo: String, arrayMessage: Array) { + val builder = AlertDialog.Builder(mContext) + builder.setTitle(titulo) + builder.setSingleChoiceItems( + arrayMessage, 1 + ) { _, which -> + mSelection = which + } + builder.setNegativeButton( + mContext.getString(R.string.cancel) + ) { _, _ -> } + builder.setPositiveButton( + mContext.getString(R.string.accept) + ) { _, _ -> } + val alert = builder.create() + alert.setCanceledOnTouchOutside(false) + alert.show() + } + + fun showRadioButtonMessage( + titulo: String, + arrayMessage: Array, + onClickActionButton: OnClickActionButton + ): Dialog { + val builder = AlertDialog.Builder(mContext, R.style.DialogMessageTheme) + builder.setTitle(titulo) + builder.setSingleChoiceItems(arrayMessage, 0) { _, which -> mSelection = which } + builder.setPositiveButton(mContext.getString(R.string.accept)) { _, _ -> + onClickActionButton.onPositiveRadioButtonClicked( + mSelection + ) + } + builder.setCancelable(false) + builder.setNegativeButton(mContext.getString(R.string.cancel)) { _, _ -> onClickActionButton.onNegativeButtonClicked() } + return builder.create() + } + + private fun showDialog(titulo: String, descripcion: String) { + if (mDialog.isShowing) + mDialog.dismiss() + + mDialog = ProgressDialog(mContext, R.style.DialogMessageTheme) + mDialog.setTitle(titulo) + mDialog.setMessage(descripcion) + mDialog.setCancelable(false) + mDialog.setCanceledOnTouchOutside(false) + mDialog.show() + } + + private fun showWebViewDialog(view: View) { + if (mDialog.isShowing) + mDialog.dismiss() + + mDialog = ProgressDialog(mContext, R.style.DialogMessageTheme) + mDialog.setView(view) + mDialog.setCancelable(false) + mDialog.setCanceledOnTouchOutside(false) + mDialog.show() + } + + private fun showAlertDialogMessage( + title: String, + message: String, + positiveButton: String + ): Dialog { + val builder = AlertDialog.Builder(mContext) + builder.setTitle(title) + builder.setMessage(message) + builder.setPositiveButton(positiveButton) { _, _ -> } + return builder.create() + } + + interface OnClickActionButton { + fun onPositiveRadioButtonClicked(selection: Int) + fun onPositiveButtonClicked() + fun onNegativeButtonClicked() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/CustomHtmlToPdfConvertor.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/CustomHtmlToPdfConvertor.kt new file mode 100644 index 0000000..2e1a15b --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/CustomHtmlToPdfConvertor.kt @@ -0,0 +1,96 @@ +package com.iesoluciones.siodrenax.utils + +import android.content.Context +import android.os.Build +import android.print.PdfPrinter +import android.print.PrintAttributes +import android.print.PrintDocumentAdapter +import android.webkit.WebChromeClient +import android.webkit.WebView +import androidx.annotation.UiThread +import java.io.File + +class CustomHtmlToPdfConvertor(private val context: Context) { + + private var baseUrl: String? = null + + fun setBaseUrl(baseUrl: String) { + this.baseUrl = baseUrl + } + + @UiThread + fun convert( + pdfLocation: File, + htmlString: String, + onPdfGenerationFailed: PdfGenerationFailedCallback? = null, + onPdfGenerated: PdfGeneratedCallback, + ) { + + // maintain pdf generation status + var pdfGenerationStarted = false + try { + + // create new webview + val pdfWebView = WebView(context) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + pdfWebView.settings.safeBrowsingEnabled = false + } + + // job name + val jobName = Math.random().toString() + + // generate pdf attributes and properties + val attributes = getPrintAttributes() + + // generate print document adapter + val printAdapter = getPrintAdapter(pdfWebView, jobName) + + pdfWebView.webChromeClient = object : WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + + // some times progress provided by this method is wrong, that's why we are getting progress directly provided by WebView + val progress = pdfWebView.progress + + + // when page is fully loaded, start creating PDF + if (progress == 100 && !pdfGenerationStarted) { + + // change the status of pdf generation + pdfGenerationStarted = true + + // generate pdf + val pdfPrinter = PdfPrinter(attributes) + pdfPrinter.generate(printAdapter, pdfLocation, onPdfGenerated) + } + } + } + + // load html in WebView when it's setup is completed + pdfWebView.loadDataWithBaseURL(baseUrl, htmlString, "text/html", "utf-8", null) + + } catch (e: Exception) { + onPdfGenerationFailed?.invoke(e) + } + } + + + private fun getPrintAdapter(pdfWebView: WebView, jobName: String): PrintDocumentAdapter { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + pdfWebView.createPrintDocumentAdapter(jobName) + } else { + pdfWebView.createPrintDocumentAdapter() + } + } + + private fun getPrintAttributes(): PrintAttributes { + return PrintAttributes.Builder().apply { + setMediaSize(PrintAttributes.MediaSize.NA_LETTER) + setResolution(PrintAttributes.Resolution("pdf", Context.PRINT_SERVICE, 100, 100)) + setMinMargins(PrintAttributes.Margins.NO_MARGINS) + }.build() + } +} + +private typealias PdfGeneratedCallback = (File) -> Unit +private typealias PdfGenerationFailedCallback = (Exception) -> Unit \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/Extensions.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/Extensions.kt new file mode 100644 index 0000000..424d6d8 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/Extensions.kt @@ -0,0 +1,76 @@ +package com.iesoluciones.siodrenax.utils + +import android.app.Activity +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import com.google.android.material.snackbar.Snackbar +import com.iesoluciones.siodrenax.R +import java.nio.file.Files +import java.nio.file.Path +import java.text.DecimalFormat + +fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) = + Toast.makeText(this, message, duration).show() + +fun Activity.toastLong(message: CharSequence, duration: Int = Toast.LENGTH_LONG) = + Toast.makeText(this, message, duration).show() + +fun Activity.toast(resourceId: Int, duration: Int = Toast.LENGTH_SHORT) = + Toast.makeText(this, resourceId, duration).show() + +fun Activity.snackBar( + message: CharSequence, + view: View? = findViewById(R.id.container), + duration: Int = Snackbar.LENGTH_SHORT, + action: String? = null, + actionEvt: (v: View) -> Unit = {} +) { + + if (view != null) { + val snackBar = Snackbar.make(view, message, duration) + if (!action.isNullOrEmpty()) { + snackBar.setAction(action, actionEvt) + } + snackBar.show() + } +} + +fun ViewGroup.inflate(layoutId: Int) = LayoutInflater.from(context).inflate(layoutId, this, false)!! + +inline fun Activity.goToActivity(noinline init: Intent.() -> Unit = {}) { + val intent = Intent(this, T::class.java) + intent.init() + startActivity(intent) +} + +fun String.currencyFormat(): String { + return try { + val formatter = DecimalFormat("###,###,##0.00") + formatter.format(this.toDouble()) + } catch (e: Exception) { + "0.00" + } +} + +fun Path.exists(): Boolean = Files.exists(this) + +fun Path.isFile(): Boolean = !Files.isDirectory(this) + +fun Path.delete(): Boolean { + return if (isFile() && exists()) { + //Actual delete operation + Files.delete(this) + true + } else { + false + } +} + +val Any.TAG: String + get() { + val tag = javaClass.simpleName + return if (tag.length <= 23) tag else tag.substring(0, 23) + } \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/HelperUtil.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/HelperUtil.kt new file mode 100644 index 0000000..058e967 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/HelperUtil.kt @@ -0,0 +1,464 @@ +package com.iesoluciones.siodrenax.utils + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Handler +import android.os.Looper +import android.os.StrictMode +import android.provider.Settings +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager +import androidx.work.Constraints +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import com.iesoluciones.siodrenax.* +import com.iesoluciones.siodrenax.App.Companion.context +import com.iesoluciones.siodrenax.activities.LoginActivity +import com.iesoluciones.siodrenax.entities.* +import com.iesoluciones.siodrenax.fragments.dialogs.IncidentsInputFragment +import com.iesoluciones.siodrenax.fragments.dialogs.MileageInputDialogFragment +import com.iesoluciones.siodrenax.fragments.dialogs.VehicleIncidentsFragment +import com.iesoluciones.siodrenax.interfaces.OnIncidentListener +import com.iesoluciones.siodrenax.interfaces.OnIncidentShowListener +import com.iesoluciones.siodrenax.interfaces.OnMileageListener +import com.iesoluciones.siodrenax.network.HttpError +import com.iesoluciones.siodrenax.network.UnprocessableEntity +import com.iesoluciones.siodrenax.repositories.WorkdayRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.BUFFER_SIZE +import com.iesoluciones.siodrenax.utils.Constants.Companion.BUFFER_SIZE_SKETCH +import com.iesoluciones.siodrenax.workers.SyncWorker +import retrofit2.HttpException +import java.io.* +import java.net.* +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import javax.net.ssl.SSLPeerUnverifiedException + + +class HelperUtil { + + private val tagIncidenceDialog = "incidenceDialog" + private val tagShowIncidenceDialog = "showIncidenceDialog" + private val tagMileageDialog = "mileageDialog" + + fun parseError(context: Context, e: Throwable) { + + if (e is HttpException) { + when (e.code()) { + 401, 400, 403, 404, 405, 423 -> { + val error: HttpError? = HttpError.parseException(e) + if (error != null) { + AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(error.error) + .setTitle(R.string.title_error) + .setCancelable(false) + .setPositiveButton( + App.shareInstance!!.resources.getString(R.string.button_ok), + null + ) + .show() + } + } + 420 -> { + val error: HttpError? = HttpError.parseException(e) + if (error != null) { + val workdayRepository = WorkdayRepository() + if (workdayRepository.validateRenewSession()) { + AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(R.string.session_expired_message) + .setTitle(R.string.session_expired) + .setCancelable(false) + .setPositiveButton(App.shareInstance!!.resources.getString(R.string.button_renew_session)) { _, _ -> + workdayRepository.logout() + val intent = Intent(context, LoginActivity::class.java); + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + (context as Activity).startActivity(intent) + (context as Activity).finish() + } + .show() + } else { + AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(R.string.session_expired_with_orders_message) + .setTitle(R.string.session_expired) + .setCancelable(false) + .setPositiveButton(App.shareInstance!!.resources.getString(R.string.button_ok), null) + .show() + } + + + } + } + 422 -> { + val errors = UnprocessableEntity.parseException(e) + AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(errors!!.errors[0]) + .setTitle(errors.message) + .setCancelable(false) + .setPositiveButton( + App.shareInstance!!.resources.getString(R.string.button_ok), + null + ) + .show() + } + 500 -> AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(App.shareInstance!!.resources.getString(R.string.unexpected_error)) + .setTitle(App.shareInstance!!.resources.getString(R.string.title_error)) + .setCancelable(false) + .setPositiveButton(context.resources.getString(R.string.button_ok), null) + .show() + } + } else if (e is IOException) { + when (e) { + is ConnectException -> AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(App.shareInstance!!.resources.getString(R.string.unreachable_network)) + .setTitle(App.shareInstance!!.resources.getString(R.string.title_error)) + .setCancelable(false) + .setPositiveButton( + App.shareInstance!!.resources.getString(R.string.button_ok), + null + ) + .setNeutralButton(App.shareInstance!!.resources.getString(R.string.network_settings)) { _, _ -> + context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) + }.show() + + is SocketException -> AlertDialog.Builder(context, R.style.MyAlertDialogStyle) + .setMessage(App.shareInstance!!.resources.getString(R.string.unreachable_network)) + .setTitle(App.shareInstance!!.resources.getString(R.string.title_error)) + .setCancelable(false) + .setPositiveButton( + App.shareInstance!!.resources.getString(R.string.button_ok), + null + ) + .setNeutralButton(App.shareInstance!!.resources.getString(R.string.network_settings)) { _, _ -> + context.startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS)) + }.show() + + is SocketTimeoutException -> AlertDialog.Builder( + context, + R.style.MyAlertDialogStyle + ) + .setMessage(App.shareInstance!!.resources.getString(R.string.timeout)) + .setTitle(App.shareInstance!!.resources.getString(R.string.title_error)) + .setCancelable(false) + .setPositiveButton( + App.shareInstance!!.resources.getString(R.string.button_ok), + null + ) + .show() + + is UnknownHostException -> Toast.makeText( + context, + context.getString(R.string.unknown_host_exception), + Toast.LENGTH_LONG + ).show() + + is SSLPeerUnverifiedException -> AlertDialog.Builder( + context, + R.style.MyAlertDialogStyle + ) + .setMessage(App.shareInstance!!.resources.getString(R.string.certificate_error_message)) + .setTitle(App.shareInstance!!.resources.getString(R.string.certificate_error)) + .setCancelable(false) + .setPositiveButton( + App.shareInstance!!.resources.getString(R.string.button_ok), + null + ) + .show() + + else -> Toast.makeText( + context, + context.getString(R.string.unexpected_error), + Toast.LENGTH_LONG + ).show() + } + } + } + + fun incidentShowDialogFragment( + fm: FragmentManager, + listener: OnIncidentShowListener, + incidence: String? + ) { + val ft = fm.beginTransaction() + val prev = fm.findFragmentByTag(tagShowIncidenceDialog) + if (prev != null) { + ft.remove(prev) + } + ft.addToBackStack(null) + Handler(Looper.getMainLooper()).postDelayed({ + val dialogFragment: DialogFragment = VehicleIncidentsFragment.newInstance( + listener, + incidence + ) + dialogFragment.show(ft, tagShowIncidenceDialog) + }, 500) + } + + fun createEvidences(order: Order) { + + val evidence = evidenceBox.query() + .equal(Evidence_.idOrder, order.id) + .and().equal(Evidence_.idRequest, order.idRequest) + .build() + .findFirst() + + if(evidence == null) + this.createEvidenceHolders(order.id, order.idRequest) + } + + private fun createEvidenceHolders(idOrder: Long, idRequest: Long) { + for (i in 1..9) { + val evidence = Evidence() + evidence.idOrder = idOrder + evidence.idRequest = idRequest + when (i) { + 1, 2, 3 -> { + evidence.type = Constants.EVIDENCE_START + evidence.evidenceNo = ((i.toString() + "").toLong()) + } + 4, 5, 6 -> { + evidence.type = Constants.EVIDENCE_PROCESS + evidence.evidenceNo = ((i - 3).toString() + "").toLong() + } + 7, 8, 9 -> { + evidence.type = Constants.EVIDENCE_FINAL + evidence.evidenceNo = ((i - 6).toString() + "").toLong() + } + else -> { + evidence.type = Constants.EVIDENCE_START + 1 + evidence.evidenceNo = (i.toString() + "").toLong() + } + } + + try { + evidenceBox.put(evidence) + } catch (ex: Exception) { + Log.e("CREAR EVIDENCIA", ex.toString()) + } + } + } + + fun mileageDialogFragment(fm: FragmentManager, listener: OnMileageListener) { + val ft = fm.beginTransaction() + val prev = fm.findFragmentByTag(tagMileageDialog) + if (prev != null) { + ft.remove(prev) + } + ft.addToBackStack(null) + Handler(Looper.getMainLooper()).postDelayed({ + val dialogFragment: DialogFragment = + MileageInputDialogFragment.newInstance(listener) + dialogFragment.show(ft, tagMileageDialog) + + }, 0) + } + + fun incidentDialogFragment(fm: FragmentManager, listener: OnIncidentListener) { + val ft = fm.beginTransaction() + val prev = fm.findFragmentByTag(tagIncidenceDialog) + if (prev != null) { + ft.remove(prev) + } + ft.addToBackStack(null) + Handler(Looper.getMainLooper()).postDelayed({ + val dialogFragment: DialogFragment = IncidentsInputFragment.newInstance(listener) + dialogFragment.show(ft, tagIncidenceDialog) + }, 500) + } + + fun formatTime(millis: Long): String { + val secs = millis / 1000 + + return String.format("%02d:%02d:%02d", secs / 3600, secs % 3600 / 60, secs % 60) + } + + fun decodeFromFile(path: String, sampleSize: Int): Bitmap { + val options2 = BitmapFactory.Options() + options2.inSampleSize = sampleSize + + return if (sampleSize != 0) BitmapFactory.decodeFile( + path, + options2 + ) else BitmapFactory.decodeFile( + path, + null + ) + } + + fun saveBitmap(filePath: File, filename: String, bitmap: Bitmap): String? { + val file = File(filePath, filename) + val fos: FileOutputStream + + try { + fos = FileOutputStream(file) + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos) + fos.flush() + } catch (e: java.lang.Exception) { + e.printStackTrace() + return null + } + + return file.absolutePath + } + + fun startSync() { + //System.gc() + + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + val syncWorker = OneTimeWorkRequest.Builder(SyncWorker::class.java) + .setConstraints(constraints) + .build() + + Toast.makeText( + context, + context!!.getString(R.string.toast_sync_progress), + Toast.LENGTH_LONG + ).show() + + WorkManager.getInstance().enqueue(syncWorker) + } + + /** + * Extracts first letter of Name and LastName sent in Params + * + * @param name Name + * @param lastname Last Name + * @return String with 2 Letters in UpperCase + */ + fun getInitials(name: String?, lastname: String?): String { + var initials = "" + if (name != null) if (name.isNotEmpty() && name.substring(0, 1) != " ") { + initials += name.substring(0, 1) + } + if (lastname != null) if (lastname.isNotEmpty() && lastname.substring(0, 1) != " ") { + initials += lastname.substring(0, 1) + } + return initials.toUpperCase(Locale.ROOT) + } + + @Throws(IOException::class) + fun zip(files: Array, zipFile: String): String { + var origin: BufferedInputStream? + val out = ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))) + try { + val data = ByteArray(BUFFER_SIZE) + for (i in files.indices) { + val fi = FileInputStream(files[i]) + origin = BufferedInputStream(fi, BUFFER_SIZE) + try { + val entry = ZipEntry(files[i].substring(files[i].lastIndexOf("/") + 1)) + out.putNextEntry(entry) + var count: Int + while (origin.read(data, 0, BUFFER_SIZE) + .also { count = it } != -1 + ) { + out.write(data, 0, count) + } + } finally { + origin.close() + } + } + } finally { + out.close() + } + + return zipFile + } + + fun deleteFile(path: String): Boolean{ + val p = Paths.get(path) + return p.delete() + } + + fun encodeBase64(filePath: String): String?{ + return try { + val bytes = File(filePath).readBytes() + Base64.getEncoder().encodeToString(bytes) + }catch (e: Exception){ + null + } + } + + fun encodeBase64WithData(filePath: String): String?{ + return try { + val bytes = File(filePath).readBytes() + val base64 = Base64.getEncoder().encodeToString(bytes) + val extension = filePath.split(".").last() + "data:image/$extension;base64,$base64" + }catch (e: Exception){ + null + } + } + + fun scaleBitmap(rotated: Bitmap, maxSize: Int = 300): Bitmap? { + val outWidth: Int + val outHeight: Int + val inWidth = rotated.width + val inHeight = rotated.height + if (inWidth > inHeight) { + outWidth = maxSize + outHeight = inHeight * maxSize / inWidth + } else { + outHeight = maxSize + outWidth = inWidth * maxSize / inHeight + } + + return Bitmap.createScaledBitmap(rotated, outWidth, outHeight, false) + } + + fun downloadFile(fileUrl: String?, directory: File?, order: Order?) { + val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() + StrictMode.setThreadPolicy(policy) + try { + val url = URL(fileUrl) + val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection + urlConnection.connect() + val inputStream: InputStream = urlConnection.inputStream + val fileOutputStream = FileOutputStream(directory) + val buffer = ByteArray(BUFFER_SIZE_SKETCH) + var bufferLength = 0 + while (inputStream.read(buffer).also { bufferLength = it } > 0) { + fileOutputStream.write(buffer, 0, bufferLength) + } + fileOutputStream.close() + + if(order != null){ + order.sketchPath = directory!!.absolutePath + orderBox.put(order) + } + + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: MalformedURLException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + } + + fun downloadSketchs(filesDir:String, orders: List){ + + if(!File("$filesDir/pdf_sketchs").isDirectory){ + Files.createDirectory(Paths.get("$filesDir/pdf_sketchs")) + } + + for (order in orders) { + if(order.sketchName != null && order.sketchPath == null){ + downloadFile("${BuildConfig.STORAGE_URL}pdf_croquis/${order.sketchName}", File("$filesDir/pdf_sketchs/${order.sketchName}"), order) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/ImageUtils.java b/app/src/main/java/com/iesoluciones/siodrenax/utils/ImageUtils.java new file mode 100644 index 0000000..b676efd --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/ImageUtils.java @@ -0,0 +1,129 @@ +package com.iesoluciones.siodrenax.utils; + +import static com.iesoluciones.siodrenax.utils.Constants.COMPRESS_JPEG_QUALITY_LOW; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.media.ExifInterface; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ImageUtils { + public static ImageUtils mInstant; + + public static ImageUtils getInstant(){ + if(mInstant==null){ + mInstant = new ImageUtils(); + } + return mInstant; + } + + public Bitmap getCompressedBitmap(String imagePath) { + float maxHeight = 1920.0f; + float maxWidth = 1080.0f; + Bitmap scaledBitmap = null; + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + Bitmap bmp = BitmapFactory.decodeFile(imagePath, options); + + int actualHeight = options.outHeight; + int actualWidth = options.outWidth; + float imgRatio = (float) actualWidth / (float) actualHeight; + float maxRatio = maxWidth / maxHeight; + + if (actualHeight > maxHeight || actualWidth > maxWidth) { + if (imgRatio < maxRatio) { + imgRatio = maxHeight / actualHeight; + actualWidth = (int) (imgRatio * actualWidth); + actualHeight = (int) maxHeight; + } else if (imgRatio > maxRatio) { + imgRatio = maxWidth / actualWidth; + actualHeight = (int) (imgRatio * actualHeight); + actualWidth = (int) maxWidth; + } else { + actualHeight = (int) maxHeight; + actualWidth = (int) maxWidth; + + } + } + + options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight); + options.inJustDecodeBounds = false; + options.inDither = false; + options.inPurgeable = true; + options.inInputShareable = true; + options.inTempStorage = new byte[16 * 1024]; + + try { + bmp = BitmapFactory.decodeFile(imagePath, options); + } catch (OutOfMemoryError exception) { + exception.printStackTrace(); + + } + try { + scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888); + } catch (OutOfMemoryError exception) { + exception.printStackTrace(); + } + + float ratioX = actualWidth / (float) options.outWidth; + float ratioY = actualHeight / (float) options.outHeight; + float middleX = actualWidth / 2.0f; + float middleY = actualHeight / 2.0f; + + Matrix scaleMatrix = new Matrix(); + scaleMatrix.setScale(ratioX, ratioY, middleX, middleY); + + Canvas canvas = new Canvas(scaledBitmap); + canvas.setMatrix(scaleMatrix); + canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG)); + + ExifInterface exif = null; + try { + exif = new ExifInterface(imagePath); + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); + Matrix matrix = new Matrix(); + if (orientation == 6) { + matrix.postRotate(90); + } else if (orientation == 3) { + matrix.postRotate(180); + } else if (orientation == 8) { + matrix.postRotate(270); + } + scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); + } catch (IOException e) { + e.printStackTrace(); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, COMPRESS_JPEG_QUALITY_LOW, out); + + byte[] byteArray = out.toByteArray(); + + Bitmap updatedBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length); + + return updatedBitmap; + } + + private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + final int heightRatio = Math.round((float) height / (float) reqHeight); + final int widthRatio = Math.round((float) width / (float) reqWidth); + inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + } + final float totalPixels = width * height; + final float totalReqPixelsCap = reqWidth * reqHeight * 2; + + while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { + inSampleSize++; + } + return inSampleSize; + } +} diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/ListPaddingDecoration.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/ListPaddingDecoration.kt new file mode 100644 index 0000000..005db52 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/ListPaddingDecoration.kt @@ -0,0 +1,34 @@ +package com.iesoluciones.siodrenax.utils + +import android.content.Context +import android.graphics.Rect +import android.util.TypedValue +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ItemDecoration + +class ListPaddingDecoration(context: Context) : ItemDecoration() { + private val mPadding: Int + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + val itemPosition = parent.getChildAdapterPosition(view) + if (itemPosition == RecyclerView.NO_POSITION) { + return + } + if (itemPosition == 0) { + outRect.top = mPadding + } + val adapter = parent.adapter + if (adapter != null && itemPosition == adapter.itemCount - 1) { + outRect.bottom = mPadding + } + } + + companion object { + private const val PADDING_IN_DIPS = 8 + } + + init { + val metrics = context.resources.displayMetrics + mPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, PADDING_IN_DIPS.toFloat(), metrics).toInt() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/NotificationUtils.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/NotificationUtils.kt new file mode 100644 index 0000000..fbb8971 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/NotificationUtils.kt @@ -0,0 +1,91 @@ +package com.iesoluciones.siodrenax.utils + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.media.RingtoneManager +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.activities.OrdersActivity +import com.iesoluciones.siodrenax.utils.Constants.Companion.EXTRA_NOTIFICATION_CLICKED + +class NotificationUtils { + + fun showNotification(title: String?, message: String?) { + + val context: Context = App.context!! + val notificationId = System.currentTimeMillis().toInt() + val notificationManager = NotificationManagerCompat.from(context) + createNotificationChannel(context) + + val builder: NotificationCompat.Builder = NotificationCompat.Builder( + context, + context.getString(R.string.default_notification_channel_id) + ) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(title) + .setContentText(message) + .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) + .setPriority(NotificationCompat.PRIORITY_MAX) //FOR HEADS-UP NOTIFICATIONS + .setDefaults(Notification.DEFAULT_VIBRATE) + .setAutoCancel(true) + .setWhen(System.currentTimeMillis()) + .setOngoing(false) + + goToOrdersActivity(context, notificationId, builder) + + builder.setChannelId(context.getString(R.string.default_notification_channel_id)) + + // ----- Construct notification ----- + val notification = builder.build() + notificationManager.notify(notificationId, notification) + } + + private fun createNotificationChannel(context: Context) { + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + val description = context.getString(R.string.default_notification_channel_description) + + // ----- Configure notification channel ----- + val channel = NotificationChannel( + context.getString(R.string.default_notification_channel_id), + context.getString(R.string.default_notification_channel_name), + NotificationManager.IMPORTANCE_HIGH + ) + channel.description = description + channel.enableLights(true) + channel.lightColor = Color.RED + channel.enableVibration(true) + // ------------------------------------------ + + // Register the channel with the system + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + + private fun goToOrdersActivity( + context: Context, + notificationId: Int, + builder: NotificationCompat.Builder + ) { + val mainIntent = Intent(context, OrdersActivity::class.java) + mainIntent.putExtra(EXTRA_NOTIFICATION_CLICKED, true) + mainIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) + + val pendingIntent = PendingIntent.getActivity( + context, + notificationId, + mainIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + builder.setContentIntent(pendingIntent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/NumberFormatterTextWatcher.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/NumberFormatterTextWatcher.kt new file mode 100644 index 0000000..600c75e --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/NumberFormatterTextWatcher.kt @@ -0,0 +1,34 @@ +package com.iesoluciones.siodrenax.utils + +import android.text.Editable +import android.text.TextWatcher +import androidx.appcompat.widget.AppCompatEditText +import java.text.DecimalFormat +import java.text.NumberFormat +import java.util.* + +class NumberFormatterTextWatcher(private var editText: AppCompatEditText) : + TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + override fun afterTextChanged(editable: Editable) { + editText.removeTextChangedListener(this) + try { + var originalString = editable.toString() + if (originalString.contains(",")) { + originalString = originalString.replace(",".toRegex(), "") + } + val longval: Long = originalString.toLong() + val formatter = NumberFormat.getInstance(Locale.US) as DecimalFormat + formatter.applyPattern("#,###,###,###") + val formattedString = formatter.format(longval) + + //setting text after format to EditText + editText.setText(formattedString) + editText.setSelection(editText.text!!.length) + } catch (nfe: NumberFormatException) { + nfe.printStackTrace() + } + editText.addTextChangedListener(this) + } +} diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/StatusDrawable.kt b/app/src/main/java/com/iesoluciones/siodrenax/utils/StatusDrawable.kt new file mode 100644 index 0000000..1ab582f --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/StatusDrawable.kt @@ -0,0 +1,39 @@ +package com.iesoluciones.siodrenax.utils + +import android.graphics.* +import android.graphics.drawable.Drawable + +class StatusDrawable(private val color1: String, private val color2: String?) : Drawable() { + override fun draw(canvas: Canvas) { + val width = bounds.width().toFloat() + val height = bounds.height().toFloat() + + val radius: Float = if (width > height) { + height / 2 + } else { + width / 2 + } + val path = Path() + path.addCircle(width, + height, radius, + Path.Direction.CW) + val paint = Paint() + paint.color = Color.parseColor(color1.ifEmpty { "#ffffff" }) + paint.style = Paint.Style.FILL + val oval = RectF() + paint.style = Paint.Style.FILL_AND_STROKE + val centerX: Float = width / 2 + val centerY: Float = height / 2 + oval[centerX - radius, centerY - radius, centerX + radius] = centerY + radius + paint.isAntiAlias = true + canvas.drawArc(oval, 0f, 180f, false, paint) + if (color2 != null) paint.color = Color.parseColor(color2) + canvas.drawArc(oval, 0f, -180f, false, paint) + } + + override fun setAlpha(alpha: Int) {} + override fun setColorFilter(colorFilter: ColorFilter?) {} + override fun getOpacity(): Int { + return PixelFormat.UNKNOWN + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/utils/TouchImageView.java b/app/src/main/java/com/iesoluciones/siodrenax/utils/TouchImageView.java new file mode 100644 index 0000000..e8fb31a --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/utils/TouchImageView.java @@ -0,0 +1,1120 @@ +package com.iesoluciones.siodrenax.utils; + + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.OverScroller; +import android.widget.Scroller; + +public class TouchImageView extends androidx.appcompat.widget.AppCompatImageView { + + // + // SuperMin and SuperMax multipliers. Determine how much the image can be + // zoomed below or above the zoom boundaries, before animating back to the + // min/max zoom boundary. + // + private static final float SUPER_MIN_MULTIPLIER = .75f; + private static final float SUPER_MAX_MULTIPLIER = 1.25f; + + // + // Scale of image ranges from minScale to maxScale, where minScale == 1 + // when the image is stretched to fit view. + // + private float normalizedScale; + + // + // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal. + // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix + // saved prior to the screen rotating. + // + private Matrix matrix, prevMatrix; + + private enum State {NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM} + + private State state; + + private float minScale; + private float maxScale; + private float superMinScale; + private float superMaxScale; + private float[] m; + + private Context context; + private Fling fling; + + private ScaleType mScaleType; + + private boolean imageRenderedAtLeastOnce; + private boolean onDrawReady; + + private ZoomVariables delayedZoomVariables; + + // + // Size of view and previous view size (ie before rotation) + // + private int viewWidth, viewHeight, prevViewWidth, prevViewHeight; + + // + // Size of image when it is stretched to fit view. Before and After rotation. + // + private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight; + + private ScaleGestureDetector mScaleDetector; + private GestureDetector mGestureDetector; + private OnTouchListener userTouchListener = null; + + public TouchImageView(Context context) { + super(context); + sharedConstructing(context); + } + + public TouchImageView(Context context, AttributeSet attrs) { + super(context, attrs); + sharedConstructing(context); + } + + public TouchImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + sharedConstructing(context); + } + + private void sharedConstructing(Context context) { + super.setClickable(true); + this.context = context; + mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); + mGestureDetector = new GestureDetector(context, new GestureListener()); + matrix = new Matrix(); + prevMatrix = new Matrix(); + m = new float[9]; + normalizedScale = 1; + if (mScaleType == null) { + mScaleType = ScaleType.FIT_CENTER; + } + minScale = 1; + maxScale = 3; + superMinScale = SUPER_MIN_MULTIPLIER * minScale; + superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; + setImageMatrix(matrix); + setScaleType(ScaleType.MATRIX); + setState(State.NONE); + onDrawReady = false; + super.setOnTouchListener(new PrivateOnTouchListener()); + } + + @Override + public void setOnTouchListener(OnTouchListener l) { + userTouchListener = l; + } + + @Override + public void setImageResource(int resId) { + super.setImageResource(resId); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + savePreviousImageValues(); + fitImageToView(); + } + + @Override + public void setScaleType(ScaleType type) { + if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) { + throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); + } + if (type == ScaleType.MATRIX) { + super.setScaleType(ScaleType.MATRIX); + + } else { + mScaleType = type; + if (onDrawReady) { + // + // If the image is already rendered, scaleType has been called programmatically + // and the TouchImageView should be updated with the new scaleType. + // + setZoom(this); + } + } + } + + @Override + public ScaleType getScaleType() { + return mScaleType; + } + + /** + * Returns false if image is in initial, unzoomed state. False, otherwise. + * + * @return true if image is zoomed + */ + public boolean isZoomed() { + return normalizedScale != 1; + } + + /** + * Save the current matrix and view dimensions + * in the prevMatrix and prevView variables. + */ + private void savePreviousImageValues() { + if (matrix != null && viewHeight != 0 && viewWidth != 0) { + matrix.getValues(m); + prevMatrix.setValues(m); + prevMatchViewHeight = matchViewHeight; + prevMatchViewWidth = matchViewWidth; + prevViewHeight = viewHeight; + prevViewWidth = viewWidth; + } + } + + @Override + public Parcelable onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putParcelable("instanceState", super.onSaveInstanceState()); + bundle.putFloat("saveScale", normalizedScale); + bundle.putFloat("matchViewHeight", matchViewHeight); + bundle.putFloat("matchViewWidth", matchViewWidth); + bundle.putInt("viewWidth", viewWidth); + bundle.putInt("viewHeight", viewHeight); + matrix.getValues(m); + bundle.putFloatArray("matrix", m); + bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce); + return bundle; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + normalizedScale = bundle.getFloat("saveScale"); + m = bundle.getFloatArray("matrix"); + prevMatrix.setValues(m); + prevMatchViewHeight = bundle.getFloat("matchViewHeight"); + prevMatchViewWidth = bundle.getFloat("matchViewWidth"); + prevViewHeight = bundle.getInt("viewHeight"); + prevViewWidth = bundle.getInt("viewWidth"); + imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered"); + super.onRestoreInstanceState(bundle.getParcelable("instanceState")); + return; + } + + super.onRestoreInstanceState(state); + } + + @Override + protected void onDraw(Canvas canvas) { + onDrawReady = true; + imageRenderedAtLeastOnce = true; + if (delayedZoomVariables != null) { + setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType); + delayedZoomVariables = null; + } + super.onDraw(canvas); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + savePreviousImageValues(); + } + + /** + * Get the current zoom. This is the zoom relative to the initial + * scale, not the original resource. + * + * @return current zoom multiplier. + */ + public float getCurrentZoom() { + return normalizedScale; + } + + /** + * Reset zoom and translation to initial state. + */ + public void resetZoom() { + normalizedScale = 1; + fitImageToView(); + } + + /** + * Set zoom to the specified scale. Image will be centered around the point + * (focusX, focusY). These floats range from 0 to 1 and denote the focus point + * as a fraction from the left and top of the view. For example, the top left + * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). + * + * @param scale + * @param focusX + * @param focusY + * @param scaleType + */ + public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) { + // + // setZoom can be called before the image is on the screen, but at this point, + // image and view sizes have not yet been calculated in onMeasure. Thus, we should + // delay calling setZoom until the view has been measured. + // + if (!onDrawReady) { + delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType); + return; + } + + if (scaleType != mScaleType) { + setScaleType(scaleType); + } + resetZoom(); + scaleImage(scale, viewWidth / 2f, viewHeight / 2f, true); + matrix.getValues(m); + m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f)); + m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f)); + matrix.setValues(m); + fixTrans(); + setImageMatrix(matrix); + } + + /** + * Set zoom parameters equal to another TouchImageView. Including scale, position, + * and ScaleType. + */ + public void setZoom(com.iesoluciones.siodrenax.utils.TouchImageView img) { + PointF center = img.getScrollPosition(); + setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType()); + } + + /** + * Return the point at the center of the zoomed image. The PointF coordinates range + * in value between 0 and 1 and the focus point is denoted as a fraction from the left + * and top of the view. For example, the top left corner of the image would be (0, 0). + * And the bottom right corner would be (1, 1). + * + * @return PointF representing the scroll position of the zoomed image. + */ + public PointF getScrollPosition() { + Drawable drawable = getDrawable(); + if (drawable == null) { + return null; + } + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + + PointF point = transformCoordTouchToBitmap(viewWidth / 2f, viewHeight / 2f, true); + point.x /= drawableWidth; + point.y /= drawableHeight; + return point; + } + + /** + * Performs boundary checking and fixes the image matrix if it + * is out of bounds. + */ + private void fixTrans() { + matrix.getValues(m); + float transX = m[Matrix.MTRANS_X]; + float transY = m[Matrix.MTRANS_Y]; + + float fixTransX = getFixTrans(transX, viewWidth, getImageWidth()); + float fixTransY = getFixTrans(transY, viewHeight, getImageHeight()); + + if (fixTransX != 0 || fixTransY != 0) { + matrix.postTranslate(fixTransX, fixTransY); + } + } + + /** + * When transitioning from zooming from focus to zoom from center (or vice versa) + * the image can become unaligned within the view. This is apparent when zooming + * quickly. When the content size is less than the view size, the content will often + * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and + * then makes sure the image is centered correctly within the view. + */ + private void fixScaleTrans() { + fixTrans(); + matrix.getValues(m); + if (getImageWidth() < viewWidth) { + m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2; + } + + if (getImageHeight() < viewHeight) { + m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2; + } + matrix.setValues(m); + } + + private float getFixTrans(float trans, float viewSize, float contentSize) { + float minTrans, maxTrans; + + if (contentSize <= viewSize) { + minTrans = 0; + maxTrans = viewSize - contentSize; + + } else { + minTrans = viewSize - contentSize; + maxTrans = 0; + } + + if (trans < minTrans) + return -trans + minTrans; + if (trans > maxTrans) + return -trans + maxTrans; + return 0; + } + + private float getFixDragTrans(float delta, float viewSize, float contentSize) { + if (contentSize <= viewSize) { + return 0; + } + return delta; + } + + private float getImageWidth() { + return matchViewWidth * normalizedScale; + } + + private float getImageHeight() { + return matchViewHeight * normalizedScale; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Drawable drawable = getDrawable(); + if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { + setMeasuredDimension(0, 0); + return; + } + + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + viewWidth = setViewSize(widthMode, widthSize, drawableWidth); + viewHeight = setViewSize(heightMode, heightSize, drawableHeight); + + // + // Set view dimensions + // + setMeasuredDimension(viewWidth, viewHeight); + + // + // Fit content within view + // + fitImageToView(); + } + + /** + * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise, + * it is made to fit the screen according to the dimensions of the previous image matrix. This + * allows the image to maintain its zoom after rotation. + */ + private void fitImageToView() { + Drawable drawable = getDrawable(); + if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { + return; + } + if (matrix == null || prevMatrix == null) { + return; + } + + int drawableWidth = drawable.getIntrinsicWidth(); + int drawableHeight = drawable.getIntrinsicHeight(); + + // + // Scale image for view + // + float scaleX = (float) viewWidth / drawableWidth; + float scaleY = (float) viewHeight / drawableHeight; + + switch (mScaleType) { + case CENTER: + scaleX = scaleY = 1; + break; + + case CENTER_CROP: + scaleX = scaleY = Math.max(scaleX, scaleY); + break; + + case CENTER_INSIDE: + scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY)); + + case FIT_CENTER: + scaleX = scaleY = Math.min(scaleX, scaleY); + break; + + case FIT_XY: + break; + + default: + // + // FIT_START and FIT_END not supported + // + throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); + + } + + // + // Center the image + // + float redundantXSpace = viewWidth - (scaleX * drawableWidth); + float redundantYSpace = viewHeight - (scaleY * drawableHeight); + matchViewWidth = viewWidth - redundantXSpace; + matchViewHeight = viewHeight - redundantYSpace; + if (!isZoomed() && !imageRenderedAtLeastOnce) { + // + // Stretch and center image to fit view + // + matrix.setScale(scaleX, scaleY); + matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2); + normalizedScale = 1; + + } else { + // + // These values should never be 0 or we will set viewWidth and viewHeight + // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues + // to set them equal to the current values. + // + if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) { + savePreviousImageValues(); + } + + prevMatrix.getValues(m); + + // + // Rescale Matrix after rotation + // + m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale; + m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale; + + // + // TransX and TransY from previous matrix + // + float transX = m[Matrix.MTRANS_X]; + float transY = m[Matrix.MTRANS_Y]; + + // + // Width + // + float prevActualWidth = prevMatchViewWidth * normalizedScale; + float actualWidth = getImageWidth(); + translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth); + + // + // Height + // + float prevActualHeight = prevMatchViewHeight * normalizedScale; + float actualHeight = getImageHeight(); + translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight); + + // + // Set the matrix to the adjusted scale and translate values. + // + matrix.setValues(m); + } + fixTrans(); + setImageMatrix(matrix); + } + + /** + * Set view dimensions based on layout params + * + * @param mode + * @param size + * @param measure + * @return + */ + private int setViewSize(int mode, int size, int measure) { + int viewSize; + switch (mode) { + case MeasureSpec.AT_MOST: + viewSize = Math.min(measure, size); + break; + + case MeasureSpec.UNSPECIFIED: + viewSize = measure; + break; + + default: + viewSize = size; + break; + } + return viewSize; + } + + /** + * After rotating, the matrix needs to be translated. This function finds the area of image + * which was previously centered and adjusts translations so that is again the center, post-rotation. + * + * @param axis Matrix.MTRANS_X or Matrix.MTRANS_Y + * @param trans the value of trans in that axis before the rotation + * @param prevImageSize the width/height of the image before the rotation + * @param imageSize width/height of the image after rotation + * @param prevViewSize width/height of view before rotation + * @param viewSize width/height of view after rotation + * @param drawableSize width/height of drawable + */ + private void translateMatrixAfterRotate(int axis, float trans, float prevImageSize, float imageSize, int prevViewSize, int viewSize, int drawableSize) { + if (imageSize < viewSize) { + // + // The width/height of image is less than the view's width/height. Center it. + // + m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f; + + } else if (trans > 0) { + // + // The image is larger than the view, but was not before rotation. Center it. + // + m[axis] = -((imageSize - viewSize) * 0.5f); + + } else { + // + // Find the area of the image which was previously centered in the view. Determine its distance + // from the left/top side of the view as a fraction of the entire image's width/height. Use that percentage + // to calculate the trans in the new view width/height. + // + float percentage = (Math.abs(trans) + (0.5f * prevViewSize)) / prevImageSize; + m[axis] = -((percentage * imageSize) - (viewSize * 0.5f)); + } + } + + private void setState(State state) { + this.state = state; + } + + @Override + public boolean canScrollHorizontally(int direction) { + matrix.getValues(m); + float x = m[Matrix.MTRANS_X]; + + return (!(getImageWidth() < viewWidth)) && (!(x >= -1) || direction >= 0) && (!(Math.abs(x) + viewWidth + 1 >= getImageWidth()) || direction <= 0); + } + + /** + * Gesture Listener detects a single click or long click and passes that on + * to the view's listener. + * + * @author Ortiz + */ + private class GestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + return performClick(); + } + + @Override + public void onLongPress(MotionEvent e) { + performLongClick(); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (fling != null) { + // + // If a previous fling is still active, it should be cancelled so that two flings + // are not run simultaenously. + // + fling.cancelFling(); + } + fling = new Fling((int) velocityX, (int) velocityY); + compatPostOnAnimation(fling); + return super.onFling(e1, e2, velocityX, velocityY); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + boolean consumed = false; + if (state == State.NONE) { + float targetZoom = (normalizedScale == minScale) ? maxScale : minScale; + DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); + compatPostOnAnimation(doubleTap); + consumed = true; + } + return consumed; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + return false; + } + } + + public interface OnTouchImageViewListener { + void onMove(); + } + + /** + * Responsible for all touch events. Handles the heavy lifting of drag and also sends + * touch events to Scale Detector and Gesture Detector. + * + * @author Ortiz + */ + private class PrivateOnTouchListener implements OnTouchListener { + + // + // Remember last point position for dragging + // + private final PointF last = new PointF(); + + @Override + public boolean onTouch(View v, MotionEvent event) { + mScaleDetector.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + PointF curr = new PointF(event.getX(), event.getY()); + + if (state == State.NONE || state == State.DRAG || state == State.FLING) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + last.set(curr); + if (fling != null) + fling.cancelFling(); + setState(State.DRAG); + break; + + case MotionEvent.ACTION_MOVE: + if (state == State.DRAG) { + float deltaX = curr.x - last.x; + float deltaY = curr.y - last.y; + float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth()); + float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight()); + matrix.postTranslate(fixTransX, fixTransY); + fixTrans(); + last.set(curr.x, curr.y); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + setState(State.NONE); + break; + } + } + + setImageMatrix(matrix); + + // + // User-defined OnTouchListener + // + if (userTouchListener != null) { + userTouchListener.onTouch(v, event); + } + + // + // OnTouchImageViewListener is set: TouchImageView dragged by user. + // + + // + // indicate event was handled + // + return true; + } + } + + /** + * ScaleListener detects user two finger scaling and scales image. + * + * @author Ortiz + */ + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + setState(State.ZOOM); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true); + + // + // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user. + // + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + super.onScaleEnd(detector); + setState(State.NONE); + boolean animateToZoomBoundary = false; + float targetZoom = normalizedScale; + if (normalizedScale > maxScale) { + targetZoom = maxScale; + animateToZoomBoundary = true; + + } else if (normalizedScale < minScale) { + targetZoom = minScale; + animateToZoomBoundary = true; + } + + if (animateToZoomBoundary) { + DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2f, viewHeight / 2f, true); + compatPostOnAnimation(doubleTap); + } + } + } + + private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) { + + float lowerScale, upperScale; + if (stretchImageToSuper) { + lowerScale = superMinScale; + upperScale = superMaxScale; + + } else { + lowerScale = minScale; + upperScale = maxScale; + } + + float origScale = normalizedScale; + normalizedScale *= deltaScale; + if (normalizedScale > upperScale) { + normalizedScale = upperScale; + deltaScale = upperScale / origScale; + } else if (normalizedScale < lowerScale) { + normalizedScale = lowerScale; + deltaScale = lowerScale / origScale; + } + + matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY); + fixScaleTrans(); + } + + /** + * DoubleTapZoom calls a series of runnables which apply + * an animated zoom in/out graphic to the image. + * + * @author Ortiz + */ + private class DoubleTapZoom implements Runnable { + + private final long startTime; + private static final float ZOOM_TIME = 500; + private final float startZoom; + private final float targetZoom; + private final float bitmapX; + private final float bitmapY; + private final boolean stretchImageToSuper; + private final AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); + private final PointF startTouch; + private final PointF endTouch; + + DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) { + setState(State.ANIMATE_ZOOM); + startTime = System.currentTimeMillis(); + this.startZoom = normalizedScale; + this.targetZoom = targetZoom; + this.stretchImageToSuper = stretchImageToSuper; + PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); + this.bitmapX = bitmapPoint.x; + this.bitmapY = bitmapPoint.y; + + // + // Used for translating image during scaling + // + startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); + endTouch = new PointF(viewWidth / 2f, viewHeight / 2f); + } + + @Override + public void run() { + float t = interpolate(); + double deltaScale = calculateDeltaScale(t); + scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper); + translateImageToCenterTouchPosition(t); + fixScaleTrans(); + setImageMatrix(matrix); + + // + // OnTouchImageViewListener is set: double tap runnable updates listener + // with every frame. + // + + if (t < 1f) { + // + // We haven't finished zooming + // + compatPostOnAnimation(this); + + } else { + // + // Finished zooming + // + setState(State.NONE); + } + } + + /** + * Interpolate between where the image should start and end in order to translate + * the image so that the point that is touched is what ends up centered at the end + * of the zoom. + * + * @param t + */ + private void translateImageToCenterTouchPosition(float t) { + float targetX = startTouch.x + t * (endTouch.x - startTouch.x); + float targetY = startTouch.y + t * (endTouch.y - startTouch.y); + PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); + matrix.postTranslate(targetX - curr.x, targetY - curr.y); + } + + /** + * Use interpolator to get t + * + * @return + */ + private float interpolate() { + long currTime = System.currentTimeMillis(); + float elapsed = (currTime - startTime) / ZOOM_TIME; + elapsed = Math.min(1f, elapsed); + return interpolator.getInterpolation(elapsed); + } + + /** + * Interpolate the current targeted zoom and get the delta + * from the current zoom. + * + * @param t + * @return + */ + private double calculateDeltaScale(float t) { + double zoom = startZoom + t * (targetZoom - startZoom); + return zoom / normalizedScale; + } + } + + /** + * This function will transform the coordinates in the touch event to the coordinate + * system of the drawable that the imageview contain + * + * @param x x-coordinate of touch event + * @param y y-coordinate of touch event + * @param clipToBitmap Touch event may occur within view, but outside image content. True, to clip return value + * to the bounds of the bitmap size. + * @return Coordinates of the point touched, in the coordinate system of the original drawable. + */ + private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) { + matrix.getValues(m); + float origW = getDrawable().getIntrinsicWidth(); + float origH = getDrawable().getIntrinsicHeight(); + float transX = m[Matrix.MTRANS_X]; + float transY = m[Matrix.MTRANS_Y]; + float finalX = ((x - transX) * origW) / getImageWidth(); + float finalY = ((y - transY) * origH) / getImageHeight(); + + if (clipToBitmap) { + finalX = Math.min(Math.max(finalX, 0), origW); + finalY = Math.min(Math.max(finalY, 0), origH); + } + + return new PointF(finalX, finalY); + } + + /** + * Inverse of transformCoordTouchToBitmap. This function will transform the coordinates in the + * drawable's coordinate system to the view's coordinate system. + * + * @param bx x-coordinate in original bitmap coordinate system + * @param by y-coordinate in original bitmap coordinate system + * @return Coordinates of the point in the view's coordinate system. + */ + private PointF transformCoordBitmapToTouch(float bx, float by) { + matrix.getValues(m); + float origW = getDrawable().getIntrinsicWidth(); + float origH = getDrawable().getIntrinsicHeight(); + float px = bx / origW; + float py = by / origH; + float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px; + float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py; + return new PointF(finalX, finalY); + } + + /** + * Fling launches sequential runnables which apply + * the fling graphic to the image. The values for the translation + * are interpolated by the Scroller. + * + * @author Ortiz + */ + private class Fling implements Runnable { + + CompatScroller scroller; + int currX, currY; + + Fling(int velocityX, int velocityY) { + setState(State.FLING); + scroller = new CompatScroller(context); + matrix.getValues(m); + + int startX = (int) m[Matrix.MTRANS_X]; + int startY = (int) m[Matrix.MTRANS_Y]; + int minX, maxX, minY, maxY; + + if (getImageWidth() > viewWidth) { + minX = viewWidth - (int) getImageWidth(); + maxX = 0; + + } else { + minX = maxX = startX; + } + + if (getImageHeight() > viewHeight) { + minY = viewHeight - (int) getImageHeight(); + maxY = 0; + + } else { + minY = maxY = startY; + } + + scroller.fling(startX, startY, velocityX, velocityY, minX, + maxX, minY, maxY); + currX = startX; + currY = startY; + } + + public void cancelFling() { + if (scroller != null) { + setState(State.NONE); + scroller.forceFinished(true); + } + } + + @Override + public void run() { + + // + // OnTouchImageViewListener is set: TouchImageView listener has been flung by user. + // Listener runnable updated with each frame of fling animation. + // + + if (scroller.isFinished()) { + scroller = null; + return; + } + + if (scroller.computeScrollOffset()) { + int newX = scroller.getCurrX(); + int newY = scroller.getCurrY(); + int transX = newX - currX; + int transY = newY - currY; + currX = newX; + currY = newY; + matrix.postTranslate(transX, transY); + fixTrans(); + setImageMatrix(matrix); + compatPostOnAnimation(this); + } + } + } + + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + private static class CompatScroller { + Scroller scroller; + final OverScroller overScroller; + final boolean isPreGingerbread; + + public CompatScroller(Context context) { + isPreGingerbread = false; + overScroller = new OverScroller(context); + } + + public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { + if (isPreGingerbread) { + scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); + } else { + overScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); + } + } + + public void forceFinished(boolean finished) { + if (isPreGingerbread) { + scroller.forceFinished(finished); + } else { + overScroller.forceFinished(finished); + } + } + + public boolean isFinished() { + if (isPreGingerbread) { + return scroller.isFinished(); + } else { + return overScroller.isFinished(); + } + } + + public boolean computeScrollOffset() { + if (isPreGingerbread) { + return scroller.computeScrollOffset(); + } else { + overScroller.computeScrollOffset(); + return overScroller.computeScrollOffset(); + } + } + + public int getCurrX() { + if (isPreGingerbread) { + return scroller.getCurrX(); + } else { + return overScroller.getCurrX(); + } + } + + public int getCurrY() { + if (isPreGingerbread) { + return scroller.getCurrY(); + } else { + return overScroller.getCurrY(); + } + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private void compatPostOnAnimation(Runnable runnable) { + postOnAnimation(runnable); + } + + private static class ZoomVariables { + public final float scale; + public final float focusX; + public final float focusY; + public final ScaleType scaleType; + + public ZoomVariables(float scale, float focusX, float focusY, ScaleType scaleType) { + this.scale = scale; + this.focusX = focusX; + this.focusY = focusY; + this.scaleType = scaleType; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/CheckListViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/CheckListViewModel.kt new file mode 100644 index 0000000..3383afa --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/CheckListViewModel.kt @@ -0,0 +1,33 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.repositories.CheckListRepository + +class CheckListViewModel : ViewModel(){ + private val checkListRepository = CheckListRepository() + + var isLoading = MutableLiveData() + var checkListSuccess = MutableLiveData() + var checkListFailure = MutableLiveData() + + fun checkList() { + isLoading.value = App.context!!.resources.getString(R.string.loading_sending_information) + + checkListRepository.finalizarEncuesta( + object : CheckListRepository.CheckListInterface { + override fun success() { + isLoading.value = null + checkListSuccess.value = "" + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + checkListFailure.value = throwable + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/EvidenceViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/EvidenceViewModel.kt new file mode 100644 index 0000000..edbd808 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/EvidenceViewModel.kt @@ -0,0 +1,31 @@ +package com.iesoluciones.siodrenax.viewmodels + +import android.graphics.Bitmap +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App.Companion.shareInstance +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.models.EvidenceSignatureLocal +import com.iesoluciones.siodrenax.utils.HelperUtil +import com.iesoluciones.siodrenax.utils.ImageUtils +import java.io.File + + +class EvidenceViewModel : ViewModel() { + + var isLoading = MutableLiveData() + var onImageSaved = MutableLiveData() + + fun saveImage(filePath: File, bitmap: Bitmap) { + isLoading.value = shareInstance!!.getString(R.string.loading) + val name = System.currentTimeMillis().toString() + ".jpg" + val path: String? = HelperUtil().saveBitmap(filePath, name, bitmap) + + val compressedBitmap = ImageUtils.getInstant().getCompressedBitmap(path) + HelperUtil().saveBitmap(filePath, name, compressedBitmap) + + isLoading.value = null + onImageSaved.value = EvidenceSignatureLocal(path = path, name = name) + //System.gc() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/IncidenceViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/IncidenceViewModel.kt new file mode 100644 index 0000000..c6e96a0 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/IncidenceViewModel.kt @@ -0,0 +1,74 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.models.VehicleIncidenceResponse +import com.iesoluciones.siodrenax.repositories.IncidenceRepository + +class IncidenceViewModel : ViewModel() { + + private val incidenceRepository = IncidenceRepository() + + var isLoading = MutableLiveData() + var incidenceSuccess = MutableLiveData() + var incidenceFailure = MutableLiveData() + var getIncidenceSuccess = MutableLiveData() + var getIncidenceFailure = MutableLiveData() + + fun sendVehicleIncidence(incidence: String) { + isLoading.value = App.context!!.resources.getString(R.string.vehicle_incidence_registering_incidence) + + incidenceRepository.sendVehicleIncidenceRequest(incidence, + object : IncidenceRepository.IncidenceInterface { + override fun success(genericResponse: String?) { + isLoading.value = null + incidenceSuccess.value = genericResponse + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + incidenceFailure.value = throwable + } + } + ) + } + + fun getVehicleIncidence(vehicleId: Long) { + isLoading.value = App.context!!.resources.getString(R.string.vehicle_incidence_searching_incidence) + + incidenceRepository.getVehicleIncidenceRequest(vehicleId, + object : IncidenceRepository.GetIncidenceInterface { + override fun success(response: VehicleIncidenceResponse?) { + isLoading.value = null + getIncidenceSuccess.value = response + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + getIncidenceFailure.value = throwable + } + } + ) + } + + fun resolveVehicleIncidence(incidenceId: String) { + isLoading.value = App.context!!.resources.getString(R.string.vehicle_incidence_resolving_incidence) + + incidenceRepository.resolveVehicleIncidenceRequest(incidenceId, + object : IncidenceRepository.IncidenceInterface { + override fun success(genericResponse: String?) { + incidenceSuccess.value = genericResponse + isLoading.value = null + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + incidenceFailure.value = throwable + } + } + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/LoginViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/LoginViewModel.kt new file mode 100644 index 0000000..7ef0b54 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/LoginViewModel.kt @@ -0,0 +1,34 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.repositories.LoginRepository + +class LoginViewModel : ViewModel() { + + private val loginRepository = LoginRepository() + + var isLoading = MutableLiveData() + var loginSuccess = MutableLiveData() + var loginFailure = MutableLiveData() + + fun login(username: String, password: String, deviceId: String) { + isLoading.value = App.context!!.resources.getString(R.string.loading_login) + + loginRepository.loginRequest(username, password, deviceId, + object : LoginRepository.LoginInterface { + override fun success(result: Boolean) { + isLoading.value = null + loginSuccess.value = result + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + loginFailure.value = throwable + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/ManagerViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/ManagerViewModel.kt new file mode 100644 index 0000000..8396563 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/ManagerViewModel.kt @@ -0,0 +1,103 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.entities.Operator +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.entities.User +import com.iesoluciones.siodrenax.repositories.ManagerRepository + +class ManagerViewModel : ViewModel(){ + + var isLoading = MutableLiveData() + var onOrdersRetrieveSuccess = MutableLiveData>() + var operatorsObservable: MutableLiveData>? = null + var onCallFailure = MutableLiveData() + var onWorkdaySuccess = MutableLiveData() + private var managerRepository: ManagerRepository = ManagerRepository() + private var getUser: MutableLiveData? = null + + fun getOperatorList(): MutableLiveData>? { + if (operatorsObservable == null) { + operatorsObservable = MutableLiveData>() + operatorsObservable!!.postValue(managerRepository.getOperatorsFromDb()) + } + return operatorsObservable + } + + fun getOperatorOrders(idOperator: String) { + isLoading.value = App.shareInstance!!.resources.getString(R.string.loading_wait_a_moment) + managerRepository.getOperatorOrders( + idOperator, + object : ManagerRepository.OnOrdersRetrievedListener { + override fun success(orders: List) { + isLoading.postValue(null) + onOrdersRetrieveSuccess.postValue(orders) + } + + override fun failure(throwable: Throwable) { + isLoading.postValue(null) + onCallFailure.postValue(throwable) + } + }) + } + + fun getOperators() { + isLoading.value = App.shareInstance!!.resources.getString(R.string.loading_wait_a_moment) + managerRepository.getOperatorList( + object : ManagerRepository.OnOperatorsRetrievedListener { + override fun success(operators: List) { + operatorsObservable!!.postValue(operators) + isLoading.postValue(null) + } + + override fun failure(throwable: Throwable) { + onCallFailure.postValue(throwable) + isLoading.postValue(null) + } + }) + } + + fun endWorkload() { + isLoading.value = App.shareInstance!!.resources.getString(R.string.loading_wait_a_moment) + managerRepository.endWorkday( + object : ManagerRepository.OnWebServiceCalled { + override fun success() { + onWorkdaySuccess.postValue(true) + isLoading.postValue(null) + } + + override fun failure(e: Throwable) { + onCallFailure.postValue(e) + isLoading.postValue(null) + } + }) + } + + fun startWorkload() { + isLoading.value = App.shareInstance!!.resources.getString(R.string.loading_wait_a_moment) + managerRepository.startWorkday( + object : ManagerRepository.OnWebServiceCalled { + override fun success() { + onWorkdaySuccess.postValue(true) + isLoading.postValue(null) + } + + override fun failure(e: Throwable) { + onCallFailure.postValue(e) + isLoading.postValue(null) + } + }) + } + + fun getUser(): LiveData? { + if (getUser == null) { + getUser = MutableLiveData() + getUser!!.value = managerRepository.getUserData() + } + return getUser + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/OrderProgressViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/OrderProgressViewModel.kt new file mode 100644 index 0000000..5c67d6f --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/OrderProgressViewModel.kt @@ -0,0 +1,102 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App.Companion.context +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.adapters.OrdersAdapter.Companion.inFormatter +import com.iesoluciones.siodrenax.entities.Evidence +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import okhttp3.ResponseBody +import java.lang.Exception +import java.util.* + +class OrderProgressViewModel: ViewModel() { + + private val oneSecond = 1000L + private var ordersRepository = OrdersRepository() + var timerObservable: MutableLiveData? = null + var isLoading = MutableLiveData() + var isAllowedToFinishOrder = MutableLiveData() + var onSendSuccess = MutableLiveData() + var onSendFailure = MutableLiveData() + var onEvidenceRefresh = MutableLiveData>() + + fun getTimer(startDate: String?): LiveData? { + if (timerObservable == null) { + timerObservable = MutableLiveData() + val timer = Timer() + val startTime: Long = try { inFormatter.parse(startDate!!)!!.time } catch (e: Exception) { System.currentTimeMillis() } + // Update the elapsed time every second. + timer.scheduleAtFixedRate( + object : TimerTask() { + override fun run() { + val newValue = System.currentTimeMillis() - startTime + // setValue() cannot be called from a background thread so post to main thread. + timerObservable!!.postValue(newValue) + } + }, + oneSecond, + oneSecond + ) + } + return timerObservable + } + + /** + * Method to be used in OrderProgressActiviy to start an order + * + * @param idOrder id of the order + * @param idRequest id of the request that holds the order + * @param startDate start date in yyyy-MM-dd HH:mm:ss format + * @param startLat start latitude coordenate + * @param startLng start longitude coordenate + */ + fun startOrder( + idOrder: String, + idRequest: String, + startDate: String, + startLat: String, + startLng: String + ) { + isLoading.value = context!!.resources.getString(R.string.loading_sending_information) + ordersRepository.startOrder( + idOrder, + idRequest, + startDate, + startLat, + startLng, + object : OrdersRepository.OnOrderRequestAnswered { + override fun success(orderResponse: ResponseBody) { + isLoading.value = null + onSendSuccess.value = orderResponse + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + onSendFailure.value = throwable + } + }) + } + + fun getEvidences(idOrder: Long) { + onEvidenceRefresh.value = ordersRepository.getEvidences(idOrder) + } + + fun attemptFinishOrder(idOrder: String) { + if (ordersRepository.validateEvidences(idOrder)) + isAllowedToFinishOrder.setValue(true) + else + isAllowedToFinishOrder.setValue(false) + + } + + fun getEvidencesForZip(idOrder: Long): List{ + return ordersRepository.getEvidencesForZip(idOrder) + } + + fun getEvidencesForPdf(idOrder: Long): List{ + return ordersRepository.getEvidencesForPdf(idOrder) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/OrdersViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/OrdersViewModel.kt new file mode 100644 index 0000000..dd001ba --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/OrdersViewModel.kt @@ -0,0 +1,77 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.entities.NextDayOrder +import com.iesoluciones.siodrenax.entities.Order +import com.iesoluciones.siodrenax.repositories.OrdersRepository + +class OrdersViewModel : ViewModel() { + + private val ordersRepository: OrdersRepository = OrdersRepository() + + private var ordersObservable: MutableLiveData>? = null + private var nextDayOrdersObservable: MutableLiveData>? = null + var orderRefreshFailure = MutableLiveData() + private var showMap = MutableLiveData() + var isLoading = MutableLiveData() + + fun getOrders(): LiveData> { + if (ordersObservable == null) { + ordersObservable = MutableLiveData() + showMap.value = false + ordersObservable!!.value = ordersRepository.getOrders() + } + return ordersObservable!! + } + + fun refreshOrders(filesDir: String) { + ordersRepository.refreshOrders( + object : OrdersRepository.OnRefreshOrders { + override fun success(orders: List) { + ordersObservable!!.value = orders + isLoading.value = null + } + + override fun failure(throwable: Throwable) { + orderRefreshFailure.value = throwable + isLoading.value = null + } + } + , filesDir) + } + + fun updateOrdersFromDB() { + if (ordersObservable != null) ordersObservable!!.value = ordersRepository.getOrders() + } + + fun getNextDayOrders(): LiveData> { + if (nextDayOrdersObservable == null) { + nextDayOrdersObservable = MutableLiveData() + showMap.value = false + nextDayOrdersObservable!!.value = ordersRepository.getNextDayOrders() + } + + return nextDayOrdersObservable!! + } + + fun refreshNextDayOrders() { + ordersRepository.refreshNextDayOrders(object : OrdersRepository.OnRefreshONextDayOrders { + override fun success(nextDayOrders: List) { + nextDayOrdersObservable!!.value = nextDayOrders + isLoading.value = null + } + + override fun failure(throwable: Throwable) { + orderRefreshFailure.value = throwable + isLoading.value = null + } + }) + } + + fun updateNextDayOrdersFromDB() { + if (nextDayOrdersObservable != null) nextDayOrdersObservable!!.value = + ordersRepository.getNextDayOrders() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/SplashViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/SplashViewModel.kt new file mode 100644 index 0000000..cbc1a94 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/SplashViewModel.kt @@ -0,0 +1,73 @@ +package com.iesoluciones.siodrenax.viewmodels + +import android.os.Handler +import android.os.Looper +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import com.iesoluciones.siodrenax.utils.Constants.Companion.LOGGED_IN +import com.iesoluciones.siodrenax.utils.Constants.Companion.LOGGED_IN_MANAGER +import com.iesoluciones.siodrenax.utils.Constants.Companion.NO_SESSION +import com.iesoluciones.siodrenax.utils.Constants.Companion.ORDER_IN_PROGRESS +import com.iesoluciones.siodrenax.utils.Constants.Companion.WORKDAY_STARTED +import com.iesoluciones.siodrenax.utils.Constants.Companion.WORKDAY_STARTED_MANAGER + +class SplashViewModel : ViewModel() { + + private val ordersRepository: OrdersRepository = OrdersRepository() + var sessionStatusObservable: MutableLiveData = MutableLiveData() + private var startSplashObservable: MutableLiveData? = null + + private fun validateSession() { + + val isLogged = preferencesHelper.tokenApi != null + val workdayStarted: Boolean = preferencesHelper.workdayStarted + val isOrderInProgress = preferencesHelper.orderInProgress != 0L + val isManager: Boolean = preferencesHelper.isManager + val orderId: Long = preferencesHelper.orderInProgress!! + + Handler(Looper.getMainLooper()).postDelayed({ + + if (!isLogged) sessionStatusObservable.postValue(NO_SESSION) + + if (isLogged && !workdayStarted && !isManager) sessionStatusObservable.postValue( + LOGGED_IN + ) + + if (isLogged && !workdayStarted && isManager) sessionStatusObservable.postValue( + LOGGED_IN_MANAGER + ) + + if (workdayStarted && !isOrderInProgress && !isManager) sessionStatusObservable.postValue( + WORKDAY_STARTED + ) + + if (workdayStarted && !isOrderInProgress && isManager) sessionStatusObservable.postValue( + WORKDAY_STARTED_MANAGER + ) + + if (workdayStarted && isOrderInProgress) { + if (ordersRepository.getOrderById(orderId) == null) { + preferencesHelper.orderInProgress = null + sessionStatusObservable.postValue(WORKDAY_STARTED) + } else { + sessionStatusObservable.postValue(ORDER_IN_PROGRESS) + } + } + + }, 1000) + } + + fun getStartSplashObservable(): MutableLiveData { + + return if (startSplashObservable == null) { + startSplashObservable = MutableLiveData() + validateSession() + startSplashObservable as MutableLiveData + } else { + startSplashObservable as MutableLiveData + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/WorkdayStatusViewModel.kt b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/WorkdayStatusViewModel.kt new file mode 100644 index 0000000..501e0b2 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/viewmodels/WorkdayStatusViewModel.kt @@ -0,0 +1,110 @@ +package com.iesoluciones.siodrenax.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.iesoluciones.siodrenax.App +import com.iesoluciones.siodrenax.R +import com.iesoluciones.siodrenax.models.StartWorkdayResponse +import com.iesoluciones.siodrenax.repositories.WorkdayRepository +import okhttp3.ResponseBody + +class WorkdayStatusViewModel : ViewModel() { + private val workdayRepository: WorkdayRepository = WorkdayRepository() + + var isLoading = MutableLiveData() + + var startWorkdaySuccess = MutableLiveData() + var startWorkdayFailure = MutableLiveData() + + var endWorkdaySuccess = MutableLiveData() + var endWorkdayFailure = MutableLiveData() + + var isAllowedToLogOut = MutableLiveData() + var isAllowedToChangeVehicle = MutableLiveData() + + var changeVehicleSuccess = MutableLiveData() + var changeVehicleFailure = MutableLiveData() + + var logOutSuccessObservable = MutableLiveData() + + fun startWorkday(firebaseToken: String, odometer: String, lat: String, lng: String, filesDir: String) { + isLoading.value = App.context!!.resources.getString(R.string.loading_sending_information) + workdayRepository.startWorkdayRequest( + firebaseToken, + odometer, + lat, + lng, + filesDir, + object : WorkdayRepository.WorkdayInterface { + override fun success(response: StartWorkdayResponse) { + isLoading.value = null + startWorkdaySuccess.value = response + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + startWorkdayFailure.value = throwable + } + }) + } + + + fun endWorkday(odometer: String, lat: String, lng: String) { + isLoading.value = App.context!!.resources.getString(R.string.loading_sending_information) + workdayRepository.endWorkdayRequest( + odometer, + lat, + lng, + object : WorkdayRepository.WorkdayEndInterface { + override fun success(response: ResponseBody) { + isLoading.value = null + endWorkdaySuccess.value = response + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + endWorkdayFailure.value = throwable + } + }) + } + + fun validateChangeVehicle() { + if (workdayRepository.validateChangeVehicle()) { + isAllowedToChangeVehicle.setValue(true) + } else { + isAllowedToChangeVehicle.setValue(false) + } + } + + fun changeVehicle(odometer: String, lat: String, lng: String) { + isLoading.value = App.context!!.resources.getString(R.string.loading_sending_information) + workdayRepository.changeVehicle( + odometer, + lat, + lng, + object : WorkdayRepository.WorkdayEndInterface { + override fun success(response: ResponseBody) { + isLoading.value = null + changeVehicleSuccess.value = response + } + + override fun failure(throwable: Throwable) { + isLoading.value = null + changeVehicleFailure.value = throwable + } + }) + } + + fun logout() { + workdayRepository.logout() + logOutSuccessObservable.value = true + } + + fun validateLogOut() { + if (workdayRepository.validateLogOut()) { + isAllowedToLogOut.setValue(true) + } else { + isAllowedToLogOut.setValue(false) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/iesoluciones/siodrenax/workers/SyncWorker.kt b/app/src/main/java/com/iesoluciones/siodrenax/workers/SyncWorker.kt new file mode 100644 index 0000000..8de3456 --- /dev/null +++ b/app/src/main/java/com/iesoluciones/siodrenax/workers/SyncWorker.kt @@ -0,0 +1,74 @@ +package com.iesoluciones.siodrenax.workers + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.iesoluciones.siodrenax.entities.OrderProgress +import com.iesoluciones.siodrenax.preferencesHelper +import com.iesoluciones.siodrenax.repositories.OrdersRepository +import io.reactivex.observers.ResourceObserver +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody +import java.util.concurrent.LinkedBlockingQueue + +class SyncWorker(context: Context, workerParameters: WorkerParameters) : Worker( + context, + workerParameters +) { + override fun doWork(): Result { + + val result = LinkedBlockingQueue() + val ordersRepository = OrdersRepository() + val pendingOrders: List = ordersRepository.getPendingOrders() + + return if (pendingOrders.isNotEmpty() && preferencesHelper.isEnableToSync) { + + preferencesHelper.isEnableToSync = false + + for (o in pendingOrders) { + + val order = ordersRepository.getOrderByIdRequest(o.idRequest) + + if (order?.pdfPath != null) { + + OrdersRepository().prepareFinishOrder(o).subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(object : ResourceObserver() { + override fun onNext(responseBody: ResponseBody) { + try { + preferencesHelper.isEnableToSync = true + result.put(Result.success()) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + + override fun onError(e: Throwable) { + try { + preferencesHelper.isEnableToSync = true + result.put(Result.success()) + } catch (e1: InterruptedException) { + e1.printStackTrace() + } + } + + override fun onComplete() { + preferencesHelper.isEnableToSync = true + } + }) + } + } + + try { + result.take() + } catch (e: InterruptedException) { + preferencesHelper.isEnableToSync = true + e.printStackTrace() + Result.success() + } + + } else { + Result.success() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/scale_out_airbnb.xml b/app/src/main/res/anim/scale_out_airbnb.xml new file mode 100644 index 0000000..c50aedd --- /dev/null +++ b/app/src/main/res/anim/scale_out_airbnb.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_rigth_airbnb.xml b/app/src/main/res/anim/slide_in_rigth_airbnb.xml new file mode 100644 index 0000000..10aa9ff --- /dev/null +++ b/app/src/main/res/anim/slide_in_rigth_airbnb.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_right_airbnb.xml b/app/src/main/res/anim/slide_out_right_airbnb.xml new file mode 100644 index 0000000..b031b5e --- /dev/null +++ b/app/src/main/res/anim/slide_out_right_airbnb.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/chevron_right.png b/app/src/main/res/drawable-hdpi/chevron_right.png new file mode 100644 index 0000000..5d04645 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/chevron_right.png differ diff --git a/app/src/main/res/drawable-hdpi/circle_border.xml b/app/src/main/res/drawable-hdpi/circle_border.xml new file mode 100644 index 0000000..d13463c --- /dev/null +++ b/app/src/main/res/drawable-hdpi/circle_border.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/drenax_logo.png b/app/src/main/res/drawable-hdpi/drenax_logo.png new file mode 100644 index 0000000..b9ba15c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/drenax_logo.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_map_outline_white.png b/app/src/main/res/drawable-hdpi/ic_map_outline_white.png new file mode 100644 index 0000000..1c1ec02 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_map_outline_white.png differ diff --git a/app/src/main/res/drawable-hdpi/rounded_background_accent.xml b/app/src/main/res/drawable-hdpi/rounded_background_accent.xml new file mode 100644 index 0000000..a375ffa --- /dev/null +++ b/app/src/main/res/drawable-hdpi/rounded_background_accent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/torch.png b/app/src/main/res/drawable-hdpi/torch.png new file mode 100644 index 0000000..620addd Binary files /dev/null and b/app/src/main/res/drawable-hdpi/torch.png differ diff --git a/app/src/main/res/drawable-hdpi/torch_off.png b/app/src/main/res/drawable-hdpi/torch_off.png new file mode 100644 index 0000000..b89e27a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/torch_off.png differ diff --git a/app/src/main/res/drawable-mdpi/capture.xml b/app/src/main/res/drawable-mdpi/capture.xml new file mode 100644 index 0000000..2644e39 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/capture.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-mdpi/capture_active.xml b/app/src/main/res/drawable-mdpi/capture_active.xml new file mode 100644 index 0000000..360705c --- /dev/null +++ b/app/src/main/res/drawable-mdpi/capture_active.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-mdpi/capture_inactive.xml b/app/src/main/res/drawable-mdpi/capture_inactive.xml new file mode 100644 index 0000000..05adcfd --- /dev/null +++ b/app/src/main/res/drawable-mdpi/capture_inactive.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-mdpi/chevron_right.png b/app/src/main/res/drawable-mdpi/chevron_right.png new file mode 100644 index 0000000..f6a14d5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/chevron_right.png differ diff --git a/app/src/main/res/drawable-mdpi/drenax_logo.png b/app/src/main/res/drawable-mdpi/drenax_logo.png new file mode 100644 index 0000000..17460c7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/drenax_logo.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_map_outline_white.png b/app/src/main/res/drawable-mdpi/ic_map_outline_white.png new file mode 100644 index 0000000..7bce5b8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_map_outline_white.png differ diff --git a/app/src/main/res/drawable-mdpi/torch.png b/app/src/main/res/drawable-mdpi/torch.png new file mode 100644 index 0000000..00a514c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/torch.png differ diff --git a/app/src/main/res/drawable-mdpi/torch_off.png b/app/src/main/res/drawable-mdpi/torch_off.png new file mode 100644 index 0000000..a8241b0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/torch_off.png differ diff --git a/app/src/main/res/drawable-xhdpi/chevron_right.png b/app/src/main/res/drawable-xhdpi/chevron_right.png new file mode 100644 index 0000000..fa9447d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chevron_right.png differ diff --git a/app/src/main/res/drawable-xhdpi/drenax_logo.png b/app/src/main/res/drawable-xhdpi/drenax_logo.png new file mode 100644 index 0000000..1c8ac79 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/drenax_logo.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_map_outline_white.png b/app/src/main/res/drawable-xhdpi/ic_map_outline_white.png new file mode 100644 index 0000000..98b4e72 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_map_outline_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/torch.png b/app/src/main/res/drawable-xhdpi/torch.png new file mode 100644 index 0000000..305fb59 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/torch.png differ diff --git a/app/src/main/res/drawable-xhdpi/torch_off.png b/app/src/main/res/drawable-xhdpi/torch_off.png new file mode 100644 index 0000000..ca210bf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/torch_off.png differ diff --git a/app/src/main/res/drawable-xxhdpi/chevron_right.png b/app/src/main/res/drawable-xxhdpi/chevron_right.png new file mode 100644 index 0000000..ae66877 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/chevron_right.png differ diff --git a/app/src/main/res/drawable-xxhdpi/drenax_logo.png b/app/src/main/res/drawable-xxhdpi/drenax_logo.png new file mode 100644 index 0000000..5855a6b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/drenax_logo.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_map_outline_white.png b/app/src/main/res/drawable-xxhdpi/ic_map_outline_white.png new file mode 100644 index 0000000..b7903f7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_map_outline_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/torch.png b/app/src/main/res/drawable-xxhdpi/torch.png new file mode 100644 index 0000000..ab90092 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/torch.png differ diff --git a/app/src/main/res/drawable-xxhdpi/torch_off.png b/app/src/main/res/drawable-xxhdpi/torch_off.png new file mode 100644 index 0000000..2281a3c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/torch_off.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/chevron_right.png b/app/src/main/res/drawable-xxxhdpi/chevron_right.png new file mode 100644 index 0000000..0d81b49 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/chevron_right.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_map_outline_white.png b/app/src/main/res/drawable-xxxhdpi/ic_map_outline_white.png new file mode 100644 index 0000000..48309fa Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_map_outline_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/torch.png b/app/src/main/res/drawable-xxxhdpi/torch.png new file mode 100644 index 0000000..f92ae53 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/torch.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/torch_off.png b/app/src/main/res/drawable-xxxhdpi/torch_off.png new file mode 100644 index 0000000..3caf965 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/torch_off.png differ diff --git a/app/src/main/res/drawable/border_dotted.xml b/app/src/main/res/drawable/border_dotted.xml new file mode 100644 index 0000000..6cb6703 --- /dev/null +++ b/app/src/main/res/drawable/border_dotted.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_ripple_bg.xml b/app/src/main/res/drawable/button_ripple_bg.xml new file mode 100644 index 0000000..9b4b0bd --- /dev/null +++ b/app/src/main/res/drawable/button_ripple_bg.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border_accent.xml b/app/src/main/res/drawable/circle_border_accent.xml new file mode 100644 index 0000000..128d168 --- /dev/null +++ b/app/src/main/res/drawable/circle_border_accent.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_border_primary.xml b/app/src/main/res/drawable/circle_border_primary.xml new file mode 100644 index 0000000..8bebc3b --- /dev/null +++ b/app/src/main/res/drawable/circle_border_primary.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_camera_black.xml b/app/src/main/res/drawable/ic_camera_black.xml new file mode 100644 index 0000000..13186de --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_black.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..071825e --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_location_on_blue_24dp.xml b/app/src/main/res/drawable/ic_location_on_blue_24dp.xml new file mode 100644 index 0000000..541f734 --- /dev/null +++ b/app/src/main/res/drawable/ic_location_on_blue_24dp.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_pdf.xml b/app/src/main/res/drawable/ic_pdf.xml new file mode 100644 index 0000000..e5cdaae --- /dev/null +++ b/app/src/main/res/drawable/ic_pdf.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout-land/activity_workday.xml b/app/src/main/res/layout-land/activity_workday.xml new file mode 100644 index 0000000..676949a --- /dev/null +++ b/app/src/main/res/layout-land/activity_workday.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml new file mode 100644 index 0000000..d84003c --- /dev/null +++ b/app/src/main/res/layout/activity_camera.xml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_confirmation.xml b/app/src/main/res/layout/activity_confirmation.xml new file mode 100644 index 0000000..61ef71d --- /dev/null +++ b/app/src/main/res/layout/activity_confirmation.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_herramienta_survey.xml b/app/src/main/res/layout/activity_herramienta_survey.xml new file mode 100644 index 0000000..7224e37 --- /dev/null +++ b/app/src/main/res/layout/activity_herramienta_survey.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..d08e664 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + +