diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b75303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ce889bd --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,119 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dictionaries/gutty.xml b/.idea/dictionaries/gutty.xml new file mode 100644 index 0000000..f9729e1 --- /dev/null +++ b/.idea/dictionaries/gutty.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..5cd135a --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..37a7509 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index e1baa3f..dd5d81c 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,26 @@ # Prueba técnica - Mobile Team La prueba tiene como objetivo evaluar tus habilidades como desarrollador. -# Descripción -La prueba técnica consiste en desarrollar una pequeña aplicación con las siguientes características. +# TAREAS -* La aplicación (emisora) deberá de enviar las coordenadas de la ubicación en la cual el usuario haya presionado durante 3 segundos sobre el mapa. Como confirmación para la aplicación (emisora) se deberá de emitir un [hapatic feedback](https://material.io/design/platform-guidance/android-haptics.html#usage) como confirmación que se envió la ubicación. -* La aplicación (receptora) deberá de mostrar la coordenada que envió la aplicación (emisora) dentro del mapa con un marcador el cual deberá desaparecer 10 segundos despues de haberse mostrado. -* Dicha información se deberá enviar en tiempo real usando Firebase Real Time Database. -* Se da por entendido que la función de emisor y receptor es una sola aplicación, la función se ve definida por la selección en el tab superior como se muestra en la siguiente imagen. +* Analisis de requerimientos +* Diseño de mockups +* Diseño de la arquitectura de app +* Añadir dependencias +* Configurar sdk google maps +* Configurar geocoding API +* Configurar firebase realtime +* Configurar Google api location +* Configurar Retrofit para conectarse a geocoding api +* Añadir repositorio de datos +* Crear localizador de servicions para inyección dependendencias +* Configurar Tabs +* Configurar mapa +* Añadir permisos para ubicacion +* Crear viewmodel +* Añadir casos de uso +* Añadir patron obsever para subscribirse a cambios de la bd +* Añadir patron de factoria para pasar repositorio a viewmodel +* Configurar lista de ubicaciones enviadas -![Views](https://github.com/urbvantransit/mobile-test/blob/master/views.png?raw=true) -## Consideraciones generales - -- Se deberá escribir un README en donde se describa las tareas que se llevaron a cabo para desarrollar la prueba. -- Se evaluará la calidad del código, uso de git y practicas de programación, por lo que te recomendamos el código de tu proyecto. -- Eres libre de mejorar y/o incluir nuevas funcionalidades que demuestren tus habilidades. Ten en cuenta que también evaluaremos las mejoras y funcionalidades que incluyas. -- El proyecto deberá realizarse en Kotlin y eres libre de agregar dependencias. - -## Entrega - -Se deberá de hacer un _fork_ a este repositorio, y continuar el desarrollo de la prueba sobre el mismo. Una vez terminada la prueba se deberá hacer un _Pull Request_ al repositorio original. 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..f4ef459 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +apply plugin: 'com.google.gms.google-services' + +android { + compileSdkVersion 29 + defaultConfig { + applicationId "com.omargtz.mobiletest" + minSdkVersion 23 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'com.google.android.material:material:1.2.1' + + //firebase dababase realtime + implementation 'com.google.firebase:firebase-database:19.2.0' + implementation 'com.google.firebase:firebase-core:17.2.0' + + //google play services + implementation "com.google.android.gms:play-services-maps:$google_play_version" + implementation "com.google.android.gms:play-services-location:$google_play_version" + + //Retrofit + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + + //lottie animations + implementation "com.airbnb.android:lottie:$lottieVersion" + + //Life components + implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion" + annotationProcessor "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + + //rx + implementation "io.reactivex.rxjava2:rxandroid:$rootProject.rxAndroid" + implementation "io.reactivex.rxjava2:rxjava:$rootProject.rxJava" + + implementation "com.squareup.retrofit2:adapter-rxjava2:$rootProject.adapterRxjava2" + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + +} diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..f551c5d --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,51 @@ +{ + "project_info": { + "project_number": "874193764794", + "firebase_url": "https://urbvantest.firebaseio.com", + "project_id": "urbvantest", + "storage_bucket": "urbvantest.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:874193764794:android:d78f6e2ca7c41f24989d83", + "android_client_info": { + "package_name": "com.omargtz.mobiletest" + } + }, + "oauth_client": [ + { + "client_id": "874193764794-3k243t09hhnfk05rufg8ku4c7gda5uk3.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.omargtz.mobiletest", + "certificate_hash": "000458eca3d42f69781225ddbaf34c76548273df" + } + }, + { + "client_id": "874193764794-h9uf2htm4hb3aqu40qc9iiikn7kr81vi.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyA9l-G1T5QcDKaIuhOpJkjijZZJVkrVoSY" + }, + { + "current_key": "AIzaSyB7wn5al8vFkYyKWS1ajXR6GdCILY70vms" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "874193764794-h9uf2htm4hb3aqu40qc9iiikn7kr81vi.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_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/omargtz/mobiletest/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/omargtz/mobiletest/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..23b607a --- /dev/null +++ b/app/src/androidTest/java/com/omargtz/mobiletest/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.omargtz.mobiletest + +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.omargtz.mobiletest", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f8f5ae9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/app/App.kt b/app/src/main/java/com/omargtz/mobiletest/app/App.kt new file mode 100644 index 0000000..abe4ae9 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/app/App.kt @@ -0,0 +1,21 @@ +package com.omargtz.mobiletest.app + +import android.app.Application +import com.omargtz.mobiletest.data.LocationRepository +import com.omargtz.mobiletest.utils.ServiceLocator + +class App: Application(){ + + + val LocationRepository: LocationRepository + get() = ServiceLocator.provideTasksRepository() + + override fun onCreate() { + super.onCreate() + } + + +} + + + diff --git a/app/src/main/java/com/omargtz/mobiletest/data/LocationRepository.kt b/app/src/main/java/com/omargtz/mobiletest/data/LocationRepository.kt new file mode 100644 index 0000000..17c798a --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/LocationRepository.kt @@ -0,0 +1,14 @@ +package com.omargtz.mobiletest.data + +import com.omargtz.mobiletest.data.remote.firebase.FirebaseDbDataSource +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO +import com.omargtz.mobiletest.data.remote.geocoding.GeocodingDataSource +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import io.reactivex.Observable + +interface LocationRepository { + + fun sendLocation(locationDTO: LocationDTO) + fun loadLocations(onGetLocations: FirebaseDbDataSource.OnGetLocations) + fun loadDirection(lat: Double,lng: Double): Observable +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/LocationRepositoryImp.kt b/app/src/main/java/com/omargtz/mobiletest/data/LocationRepositoryImp.kt new file mode 100644 index 0000000..3e4d3e3 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/LocationRepositoryImp.kt @@ -0,0 +1,25 @@ +package com.omargtz.mobiletest.data + +import com.omargtz.mobiletest.data.remote.firebase.FirebaseDbDataSource +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO +import com.omargtz.mobiletest.data.remote.geocoding.GeocodingDataSource +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import io.reactivex.Observable + +class LocationRepositoryImp(val geocodingDataSource: GeocodingDataSource, val firebaseDatasource: FirebaseDbDataSource ):LocationRepository{ + + override fun loadLocations(onGetLocations: FirebaseDbDataSource.OnGetLocations) { + firebaseDatasource.receiverLocations(onGetLocations) + } + + override fun loadDirection(lat: Double,lng:Double): Observable { + return geocodingDataSource.getDirection(lat,lng) + } + + override fun sendLocation(locationDTO: LocationDTO) { + firebaseDatasource.sendLocation(locationDTO) + } +} + + + diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseDbDataSource.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseDbDataSource.kt new file mode 100644 index 0000000..b011a5c --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseDbDataSource.kt @@ -0,0 +1,17 @@ +package com.omargtz.mobiletest.data.remote.firebase + +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO + +interface FirebaseDbDataSource { + + interface OnGetLocations{ + fun onSucess(locations: List) + fun onEmpty() + fun onError() + } + + + + fun sendLocation(location: LocationDTO) + fun receiverLocations(onGetLocations: OnGetLocations) +} diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseDbDataSourceImp.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseDbDataSourceImp.kt new file mode 100644 index 0000000..cb04d5e --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseDbDataSourceImp.kt @@ -0,0 +1,40 @@ +package com.omargtz.mobiletest.data.remote.firebase + +import com.google.android.gms.tasks.OnSuccessListener +import com.google.android.gms.tasks.Task +import com.google.firebase.database.* +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO + +class FirebaseDbDataSourceImp(val dbReference: DatabaseReference) :FirebaseDbDataSource{ + + private val CHILD = "locations" + + override fun receiverLocations(onGetLocations: FirebaseDbDataSource.OnGetLocations) { + val locationListener = object : ValueEventListener{ + override fun onCancelled(data: DatabaseError) { + onGetLocations.onError() + } + override fun onDataChange(dataSnap: DataSnapshot) { + val locations = ArrayList() + for (data in dataSnap.children){ + val location = data.getValue(LocationDTO::class.java) + locations.add(location!!) + } + if(locations.isEmpty()){ + onGetLocations.onEmpty() + }else{ + onGetLocations.onSucess(locations) + } + } + } + + dbReference.child(CHILD).addValueEventListener(locationListener) + } + + override fun sendLocation(location: LocationDTO) { + dbReference.child(CHILD).child(location.dateTime.toString()).setValue(location) + + } +} + + diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseService.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseService.kt new file mode 100644 index 0000000..f1b8872 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/FirebaseService.kt @@ -0,0 +1,16 @@ +package com.omargtz.mobiletest.data.remote.firebase + +import com.google.firebase.database.DatabaseReference +import com.google.firebase.database.FirebaseDatabase + +object FirebaseService { + + val firebaseDb: FirebaseDatabase + get() = FirebaseDatabase.getInstance() + + val dbReference: DatabaseReference + get() = firebaseDb.reference + + + +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/model/LocationDTO.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/model/LocationDTO.kt new file mode 100644 index 0000000..c15b2e0 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/firebase/model/LocationDTO.kt @@ -0,0 +1,9 @@ +package com.omargtz.mobiletest.data.remote.firebase.model + +data class LocationDTO( + val lat: Double? = 0.0, + val lng: Double?= 0.0, + val direction: String? = "", + val dateTime: Long = 0){ + +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingDataSource.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingDataSource.kt new file mode 100644 index 0000000..4f880b2 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingDataSource.kt @@ -0,0 +1,10 @@ +package com.omargtz.mobiletest.data.remote.geocoding + +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import io.reactivex.Observable + +interface GeocodingDataSource { + + fun getDirection(lat: Double, lng: Double): Observable + +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingDataSourceImp.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingDataSourceImp.kt new file mode 100644 index 0000000..9733d50 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingDataSourceImp.kt @@ -0,0 +1,17 @@ +package com.omargtz.mobiletest.data.remote.geocoding + +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import io.reactivex.Observable +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.lang.StringBuilder + +class GeocodingDataSourceImp(private val geocodingApi:GoogleGeocodingApi ):GeocodingDataSource { + + override fun getDirection(lat: Double, lng: Double):Observable{ + val latLng = StringBuilder().append(lat).append(",").append(lng).toString() + return geocodingApi.getDirection(latLng) + } +} + diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingService.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingService.kt new file mode 100644 index 0000000..83e3d07 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GeocodingService.kt @@ -0,0 +1,18 @@ +package com.omargtz.mobiletest.data.remote.geocoding + +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory + +import retrofit2.converter.gson.GsonConverterFactory + +object GeocodingService { + + val geocodingServiceProvider: GoogleGeocodingApi + get() = Retrofit.Builder() + .baseUrl("https://maps.googleapis.com/maps/") + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(GoogleGeocodingApi::class.java) + +} diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GoogleGeocodingApi.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GoogleGeocodingApi.kt new file mode 100644 index 0000000..76875c3 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/geocoding/GoogleGeocodingApi.kt @@ -0,0 +1,15 @@ +package com.omargtz.mobiletest.data.remote.geocoding + +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import io.reactivex.Observable +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Query + + +interface GoogleGeocodingApi { + + @GET("api/geocode/json?key=AIzaSyBHHlRJ8j6ME__Y4oPv9zx1OLKgH48SQFs") + fun getDirection(@Query("latlng") latlng: String): Observable + +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/AddressComponent.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/AddressComponent.kt new file mode 100644 index 0000000..2fbe5fa --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/AddressComponent.kt @@ -0,0 +1,11 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + + +data class AddressComponent( + @SerializedName("long_name") var longName: String?, + @SerializedName("short_name") var shortName: String?, + @SerializedName("types") var types: List? +) \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/GeocodingResponse.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/GeocodingResponse.kt new file mode 100644 index 0000000..958bd7a --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/GeocodingResponse.kt @@ -0,0 +1,11 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + + +data class GeocodingResponse( + @SerializedName("plus_code")var plusCode: PlusCode?, + @SerializedName("results") var results: List?, + @SerializedName("status") var status:String? +) diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Geometry.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Geometry.kt new file mode 100644 index 0000000..253ba71 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Geometry.kt @@ -0,0 +1,12 @@ +package com.omargtz.mobiletest.data.remote.model + +import android.view.View + +import com.google.gson.annotations.SerializedName + +data class Geometry( + @SerializedName("location") var location: Location?, + @SerializedName("location_type") var locationType: String?, + @SerializedName("viewport") var viewPort: ViewPort? +) + diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Location.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Location.kt new file mode 100644 index 0000000..0dfa0cd --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Location.kt @@ -0,0 +1,8 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.SerializedName + +data class Location( + @SerializedName("Lat") var lat: Double, + @SerializedName("lng") var lng: Double +) diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Northeast.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Northeast.kt new file mode 100644 index 0000000..4cf845c --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Northeast.kt @@ -0,0 +1,8 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.SerializedName + +data class Northeast( + @SerializedName("lat") var lat: Double?, + @SerializedName("lng") var lng: Double? +) diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/PlusCode.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/PlusCode.kt new file mode 100644 index 0000000..32f316b --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/PlusCode.kt @@ -0,0 +1,8 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.SerializedName + +data class PlusCode( + @SerializedName("compound_code") var compoundCode: String?, + @SerializedName("global_code") var globalCode: String? +) diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Result.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Result.kt new file mode 100644 index 0000000..205e35a --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Result.kt @@ -0,0 +1,14 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + + +data class Result( + @SerializedName("address_components") var addressComponents: List?, + @SerializedName("formatted_address") var formattedAddress: String?, + @SerializedName("geometry") var geometry: Geometry?, + @SerializedName("place_id") var placeId: String?, + @SerializedName("plus_code") var plusCode: PlusCode?, + @SerializedName("types") var types: List? +) \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Southwest.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Southwest.kt new file mode 100644 index 0000000..80b592a --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/Southwest.kt @@ -0,0 +1,8 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.SerializedName + +data class Southwest( + @SerializedName("lat") var lat:Double?, + @SerializedName("lng") var lng: Double? +) diff --git a/app/src/main/java/com/omargtz/mobiletest/data/remote/model/ViewPort.kt b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/ViewPort.kt new file mode 100644 index 0000000..3d3adc1 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/data/remote/model/ViewPort.kt @@ -0,0 +1,8 @@ +package com.omargtz.mobiletest.data.remote.model + +import com.google.gson.annotations.SerializedName + +data class ViewPort( + @SerializedName("northeast") var northeast: Northeast?, + @SerializedName("southwest") var southwest: Southwest? +) diff --git a/app/src/main/java/com/omargtz/mobiletest/location/view/LocationActivity.kt b/app/src/main/java/com/omargtz/mobiletest/location/view/LocationActivity.kt new file mode 100644 index 0000000..0663c3e --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/location/view/LocationActivity.kt @@ -0,0 +1,65 @@ +package com.omargtz.mobiletest.location.view + +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProviders +import androidx.viewpager.widget.ViewPager +import com.google.android.material.tabs.TabLayout +import com.omargtz.mobiletest.R +import com.omargtz.mobiletest.app.App +import com.omargtz.mobiletest.location.view.adapter.ViewPagerAdapter +import com.omargtz.mobiletest.location.view.fragment.TransmitterFragment +import com.omargtz.mobiletest.location.viewmodel.LocationViewModel +import com.omargtz.mobiletest.utils.ViewModelFactory + +class LocationActivity : AppCompatActivity() { + + private lateinit var toolbar: androidx.appcompat.widget.Toolbar + private lateinit var viewModel: LocationViewModel + private lateinit var pagerAdapter: ViewPagerAdapter + + val Context.app: App + get() = applicationContext as App + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_location) + setViewModel() + initViews() + initToolbar() + //setupMap() + } + + private fun initViews(){ + toolbar = findViewById(R.id.home_toolbar) + + } + + private fun initToolbar(){ + setSupportActionBar(toolbar) + supportActionBar?.title = getString(R.string.location_title) + } + + + private fun setViewModel(){ + val viewModelFactory = ViewModelFactory(app.LocationRepository) + viewModel = ViewModelProviders.of(this,viewModelFactory)[LocationViewModel::class.java] + + } + + + + private fun setupMap(){ + supportFragmentManager.beginTransaction() + .add(R.id.fragment_content, TransmitterFragment()) + .commit() + } + + + +} diff --git a/app/src/main/java/com/omargtz/mobiletest/location/view/adapter/LocationsAdapter.kt b/app/src/main/java/com/omargtz/mobiletest/location/view/adapter/LocationsAdapter.kt new file mode 100644 index 0000000..6325aad --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/location/view/adapter/LocationsAdapter.kt @@ -0,0 +1,42 @@ +package com.omargtz.mobiletest.location.view.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.omargtz.mobiletest.R +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO +import com.omargtz.mobiletest.location.viewmodel.LocationViewModel +import kotlinx.android.synthetic.main.item_location.view.* + +class LocationsAdapter(var locations: List,val locationViewModel:LocationViewModel): + RecyclerView.Adapter() { + + override fun getItemCount(): Int { + return locations.size + } + + // Inflates the item views + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocationViewHolder { + return LocationViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_location, parent, false)) + } + + // Binds each animal in the ArrayList to a view + override fun onBindViewHolder(holder: LocationViewHolder, position: Int) { + holder.tvDirection?.text = locations.get(holder.adapterPosition).direction + holder.itemView.setOnClickListener(){ + _ -> locationViewModel.clickItemLocation(locations.get(holder.adapterPosition)) + } + } + public fun updateList(newlocations: List){ + locations = newlocations + notifyDataSetChanged() + } + +} + class LocationViewHolder (view: View) : RecyclerView.ViewHolder(view) { + // Holds the TextView that will add each animal to + val tvDirection = view.item_tv_direction; + } + + diff --git a/app/src/main/java/com/omargtz/mobiletest/location/view/adapter/ViewPagerAdapter.kt b/app/src/main/java/com/omargtz/mobiletest/location/view/adapter/ViewPagerAdapter.kt new file mode 100644 index 0000000..91c2ce6 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/location/view/adapter/ViewPagerAdapter.kt @@ -0,0 +1,31 @@ +package com.omargtz.mobiletest.location.view.adapter + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import com.omargtz.mobiletest.location.view.fragment.ReceiverFragment +import com.omargtz.mobiletest.location.view.fragment.TransmitterFragment + + +class ViewPagerAdapter(manager: FragmentManager, titles: List): FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT){ + + private val COUNT = 2 + private val RECEIVER = 0 + private val TRANSMITTER = 1; + private var titles = titles + + override fun getItem(position: Int): Fragment { + return when (position) { + RECEIVER -> TransmitterFragment() + else -> ReceiverFragment() + } + } + + override fun getPageTitle(position: Int): CharSequence? { + return titles[position] + } + + override fun getCount(): Int { + return titles.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/location/view/fragment/ReceiverFragment.kt b/app/src/main/java/com/omargtz/mobiletest/location/view/fragment/ReceiverFragment.kt new file mode 100644 index 0000000..ef6404a --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/location/view/fragment/ReceiverFragment.kt @@ -0,0 +1,162 @@ +package com.omargtz.mobiletest.location.view.fragment + + +import android.location.Location +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions + +import com.omargtz.mobiletest.R +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO +import com.omargtz.mobiletest.location.view.adapter.LocationsAdapter +import com.omargtz.mobiletest.location.viewmodel.LocationViewModel +import com.omargtz.mobiletest.utils.Event +import kotlinx.android.synthetic.* + +class ReceiverFragment : Fragment(), OnMapReadyCallback { + private lateinit var mapView: MapView + private var mMap: GoogleMap? = null + private lateinit var viewmodel: LocationViewModel + private lateinit var listOfLocations: RecyclerView + private lateinit var locationsAdapter: LocationsAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_receiver, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mapView = view.findViewById(R.id.receiver_map_view) + listOfLocations = view.findViewById(R.id.list_of_locations) + mapView.onCreate(savedInstanceState) + mapView.getMapAsync(this) + + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + viewmodel = ViewModelProviders.of(activity!!)[LocationViewModel::class.java] + setupListLocations() + subscribeLocationsError() + subscribeLoadLocations() + subscribeClickItemLocationEvent() + } + + override fun onStart() { + super.onStart() + mapView.onStart() + + } + + override fun onStop() { + super.onStop() + mapView.onStop() + } + + override fun onPause() { + super.onPause() + mapView.onPause() + } + + override fun onResume() { + super.onResume() + mapView.onResume() + viewmodel.loadLocations() + } + + override fun onDestroy() { + super.onDestroy() + mapView.onDestroy() + } + + override fun onLowMemory() { + super.onLowMemory() + mapView.onLowMemory() + } + + override fun onMapReady(googleMap: GoogleMap?) { + mMap = googleMap + initMap() + } + + private fun initMap(){ + mMap!!.isMyLocationEnabled = true + mMap!!.uiSettings.isMyLocationButtonEnabled = true; + mMap!!.uiSettings.isCompassEnabled = true + } + + private fun moveCamera(lat: Double, lng: Double ){ + val cameraUpdate = CameraUpdateFactory.newLatLngZoom(LatLng(lat,lng),15.0f) + mMap!!.animateCamera(cameraUpdate) + } + + fun addMarkers( locations: List) { + for (locationDTO in locations){ + val latLng = LatLng(locationDTO.lat!!,locationDTO.lng!!) + mMap!!.addMarker( + MarkerOptions() + .position(latLng) + .title("marker") + .draggable(false) + .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_marker))) + } + } + + private fun setupListLocations(){ + locationsAdapter = LocationsAdapter(viewmodel.mLocations.value!!,viewmodel) + listOfLocations.adapter = locationsAdapter; + listOfLocations.layoutManager = LinearLayoutManager(context!!,LinearLayoutManager.HORIZONTAL,false) + + } + + private fun subscribeLoadLocations(){ + viewmodel.mLocations.observe(this, object : Observer>{ + override fun onChanged(locations: List?) { + if(locations!=null){ + locationsAdapter.updateList(locations) + addMarkers(locations) + } + } + }) + } + + private fun subscribeLocationsError(){ + viewmodel.locationErrorEvent.observe(this,object : Observer>{ + override fun onChanged(event: Event?) { + if(event!!.getContentIfNotHandled()!=null){ + Log.e("Locations","ERROR") + } + } + }) + } + + private fun subscribeClickItemLocationEvent(){ + viewmodel.clickLocatinEvent.observe(this,object : Observer>{ + override fun onChanged(event: Event?) { + val locationItem = event!!.getContentIfNotHandled(); + if(locationItem!=null){ + moveCamera(locationItem.lat!!,locationItem.lng!!) + } + } + }) + } +} diff --git a/app/src/main/java/com/omargtz/mobiletest/location/view/fragment/TransmitterFragment.kt b/app/src/main/java/com/omargtz/mobiletest/location/view/fragment/TransmitterFragment.kt new file mode 100644 index 0000000..4f1b450 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/location/view/fragment/TransmitterFragment.kt @@ -0,0 +1,206 @@ +package com.omargtz.mobiletest.location.view.fragment + + +import android.Manifest +import android.annotation.SuppressLint +import android.content.pm.PackageManager +import android.location.Location +import android.os.Bundle +import android.util.Log +import android.view.Gravity +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import android.widget.Toast +import androidx.core.content.ContextCompat.checkSelfPermission +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import com.airbnb.lottie.LottieAnimationView +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions + +import com.omargtz.mobiletest.R +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import com.omargtz.mobiletest.location.viewmodel.LocationViewModel +import com.omargtz.mobiletest.utils.Event + +/** + * A simple [Fragment] subclass. + */ +class TransmitterFragment : Fragment(), OnMapReadyCallback, GoogleMap.OnCameraIdleListener, + GoogleMap.OnCameraMoveListener { + + private lateinit var mapView: MapView + private var mMap: GoogleMap? = null + private lateinit var fusedLocationClient: FusedLocationProviderClient + private val LOCATION_PERMISSION = 42 + private lateinit var viewmodel: LocationViewModel + private var marker: Marker? = null + private lateinit var btnSendLocation: Button + private lateinit var tvDirection: TextView + private lateinit var animationSearchLocation: LottieAnimationView + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_transmitter, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewmodel = ViewModelProviders.of(activity!!)[LocationViewModel::class.java] + setLocationClient() + subscribeLoadDirection() + subscribeloadDirectionConnectionErrorEvent() + subscribeloadDirectionErrorEvent() + + mapView = view.findViewById(R.id.transmiter_map_view) + btnSendLocation = view.findViewById(R.id.btn_send_location); + + btnSendLocation.setOnClickListener(){_-> + val address = viewmodel.mDirection.value!!.results!!.get(0).formattedAddress!! + viewmodel.sendLocation(getLatLngMarker().latitude,getLatLngMarker().longitude,address) + Toast.makeText(activity,"Ubicación enviada",Toast.LENGTH_SHORT).show() + } + + tvDirection = view.findViewById(R.id.tv_direction) + animationSearchLocation = view.findViewById(R.id.animation_search_location) + mapView.onCreate(savedInstanceState) + mapView.getMapAsync(this) + } + + override fun onResume() { + super.onResume() + mapView.onResume() + } + + override fun onStart() { + super.onStart() + mapView.onStart() + } + + override fun onStop() { + super.onStop() + mapView.onStop() + } + + override fun onPause() { + super.onPause() + mapView.onPause() + } + + override fun onDestroy() { + super.onDestroy() + mapView.onDestroy() + } + + override fun onLowMemory() { + super.onLowMemory() + mapView.onLowMemory() + } + + + override fun onMapReady(googleMap: GoogleMap?) { + mMap = googleMap + if (checkSelfPermission(context!!,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + initMap() + getLastLocation() + } else { + requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + LOCATION_PERMISSION) + } + } + + override fun onCameraIdle() { + viewmodel.loadDirection(getLatLngMarker().latitude,getLatLngMarker().longitude) + } + + override fun onCameraMove() { + addMarker(getLatLngMarker()) + } + + private fun getLatLngMarker():LatLng{ + return mMap!!.cameraPosition.target + } + + @SuppressLint("MissingPermission") + private fun initMap(){ + mMap!!.isMyLocationEnabled = true + mMap!!.uiSettings.isMyLocationButtonEnabled = true; + mMap!!.uiSettings.isCompassEnabled = true + mMap!!.setOnCameraIdleListener(this) + mMap!!.setOnCameraMoveListener(this) + } + + private fun setLocationClient(){ + fusedLocationClient = LocationServices.getFusedLocationProviderClient(context!!) + } + + @SuppressLint("MissingPermission") + private fun getLastLocation(){ + fusedLocationClient.lastLocation.addOnSuccessListener { location : Location -> + moveCamera(location) + viewmodel.loadDirection(location.latitude,location.longitude) + } + } + + private fun moveCamera(location: Location){ + val cameraUpdate = CameraUpdateFactory.newLatLngZoom(LatLng(location.latitude,location.longitude),15.0f) + mMap!!.animateCamera(cameraUpdate) + } + + fun addMarker(latLng: LatLng) { + if(marker==null) { + marker = mMap!!.addMarker(MarkerOptions() + .position(latLng) + .title("marker") + .draggable(false) + .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_marker))) + + }else { + marker!!.position = latLng + } + } + + private fun subscribeLoadDirection(){ + viewmodel.mDirection.observe(this,object : Observer { + override fun onChanged(geocodingDirections: GeocodingResponse?) { + if(!geocodingDirections!!.results!!.isEmpty()){ + val address = geocodingDirections.results?.get(0)!!.formattedAddress; + tvDirection.setText(address) + } + } + }) + } + + private fun subscribeloadDirectionErrorEvent(){ + viewmodel.directionErrorEvent.observe(this, + Observer> { event -> + if(event!!.getContentIfNotHandled()!=null){ + Log.e("Direction","Error") + } + }) + } + + private fun subscribeloadDirectionConnectionErrorEvent(){ + viewmodel.directionErrorConnectionEvent.observe(this, + Observer> { event -> + if(event!!.getContentIfNotHandled()!=null){ + Log.e("Direction","ErrorConnection") + } + }) + } +} diff --git a/app/src/main/java/com/omargtz/mobiletest/location/viewmodel/LocationViewModel.kt b/app/src/main/java/com/omargtz/mobiletest/location/viewmodel/LocationViewModel.kt new file mode 100644 index 0000000..de04da3 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/location/viewmodel/LocationViewModel.kt @@ -0,0 +1,103 @@ +package com.omargtz.mobiletest.location.viewmodel + +import androidx.lifecycle.* +import com.omargtz.mobiletest.data.LocationRepository +import com.omargtz.mobiletest.data.remote.firebase.FirebaseDbDataSource +import com.omargtz.mobiletest.data.remote.firebase.model.LocationDTO +import com.omargtz.mobiletest.data.remote.geocoding.GeocodingDataSource +import com.omargtz.mobiletest.data.remote.model.GeocodingResponse +import com.omargtz.mobiletest.utils.Event +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlin.math.E + +class LocationViewModel(val locationRepository: LocationRepository): ViewModel() { + + private val _mLocations = MutableLiveData>().apply { value = emptyList() } + val mLocations: LiveData> = _mLocations + + + private val _locationErrorConnectionEvent = MutableLiveData>() + + private val _locationErrorEvent = MutableLiveData>() + val locationErrorEvent: LiveData> = _locationErrorEvent + + private val _mDirection = MutableLiveData() + val mDirection : LiveData = _mDirection + + private val _directionErrorConnectionEvent = MutableLiveData>() + val directionErrorConnectionEvent: LiveData> = _directionErrorConnectionEvent + + private val _directionErrorEvent = MutableLiveData>() + val directionErrorEvent: LiveData> = _directionErrorEvent + + private val _clickLocationEvent = MutableLiveData>() + val clickLocatinEvent : LiveData> = _clickLocationEvent + + private val subscriptions = CompositeDisposable() + + + public fun clickItemLocation(location: LocationDTO){ + _clickLocationEvent.value = Event(location) + } + + fun sendLocation(lat: Double, lng: Double, direction: String ){ + val locationRequest = LocationDTO(lat,lng,direction,System.currentTimeMillis()); + locationRepository.sendLocation(locationRequest) + } + + fun loadLocations(){ + locationRepository.loadLocations(object : FirebaseDbDataSource.OnGetLocations { + + override fun onSucess(locations: List) { + _mLocations.value = locations + } + + override fun onEmpty() { + _locationErrorEvent.value = Event("NO hay ubicaciones") + } + + override fun onError() { + _locationErrorConnectionEvent.value = Event("Error de conexión") + } + }) + } + + fun loadDirection(lat:Double,lng: Double) { + val disposable = locationRepository.loadDirection(lat,lng) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { + + } + .doOnTerminate { + + } + .subscribe ({ + _mDirection.value = it + }, { + _locationErrorEvent.value = Event("Error al obtener dirección") + }) + + subscribe(disposable) + } + + fun subscribe(disposable: Disposable): Disposable { + subscriptions.add(disposable) + return disposable + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun dispose() { + subscriptions.clear() + } + + override fun onCleared() { + dispose() + super.onCleared() + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/utils/Event.kt b/app/src/main/java/com/omargtz/mobiletest/utils/Event.kt new file mode 100644 index 0000000..3214d06 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/utils/Event.kt @@ -0,0 +1,41 @@ +package com.omargtz.mobiletest.utils + +import androidx.lifecycle.Observer + +open class Event(private val content: T) { + + @Suppress("MemberVisibilityCanBePrivate") + var hasBeenHandled = false + private set // Allow external read but not write + + /** + * Returns the content and prevents its use again. + */ + fun getContentIfNotHandled(): T? { + return if (hasBeenHandled) { + null + } else { + hasBeenHandled = true + content + } + } + + /** + * Returns the content, even if it's already been handled. + */ + fun peekContent(): T = content +} + +/** + * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has + * already been handled. + * + * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. + */ +class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { + override fun onChanged(event: Event?) { + event?.getContentIfNotHandled()?.let { + onEventUnhandledContent(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/omargtz/mobiletest/utils/ServiceLocator.kt b/app/src/main/java/com/omargtz/mobiletest/utils/ServiceLocator.kt new file mode 100644 index 0000000..9a31530 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/utils/ServiceLocator.kt @@ -0,0 +1,42 @@ +package com.omargtz.mobiletest.utils + +import androidx.annotation.VisibleForTesting +import com.omargtz.mobiletest.data.LocationRepository +import com.omargtz.mobiletest.data.LocationRepositoryImp +import com.omargtz.mobiletest.data.remote.firebase.FirebaseDbDataSource +import com.omargtz.mobiletest.data.remote.firebase.FirebaseDbDataSourceImp +import com.omargtz.mobiletest.data.remote.firebase.FirebaseService +import com.omargtz.mobiletest.data.remote.geocoding.GeocodingDataSource +import com.omargtz.mobiletest.data.remote.geocoding.GeocodingDataSourceImp +import com.omargtz.mobiletest.data.remote.geocoding.GeocodingService + +object ServiceLocator{ + + @Volatile + var locationRepository: LocationRepository? = null + @VisibleForTesting set + + fun provideTasksRepository(): LocationRepository{ + synchronized(this) { + return locationRepository ?: locationRepository ?: createLocationRepository() + } + } + + + fun createLocationRepository():LocationRepository{ + return LocationRepositoryImp(createGeocodingDataSource(), createFirebaseDataSource()) + } + + + fun createGeocodingDataSource(): GeocodingDataSource{ + return GeocodingDataSourceImp(GeocodingService.geocodingServiceProvider) + } + + + fun createFirebaseDataSource():FirebaseDbDataSource{ + return FirebaseDbDataSourceImp(FirebaseService.dbReference) + } + +} + + diff --git a/app/src/main/java/com/omargtz/mobiletest/utils/ViewModelFactory.kt b/app/src/main/java/com/omargtz/mobiletest/utils/ViewModelFactory.kt new file mode 100644 index 0000000..e376872 --- /dev/null +++ b/app/src/main/java/com/omargtz/mobiletest/utils/ViewModelFactory.kt @@ -0,0 +1,22 @@ +package com.omargtz.mobiletest.utils + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.omargtz.mobiletest.data.LocationRepository +import com.omargtz.mobiletest.location.viewmodel.LocationViewModel + +@Suppress("UNCHECKED_CAST") +class ViewModelFactory constructor( + private val locationRepository: LocationRepository +) : ViewModelProvider.NewInstanceFactory() { + + override fun create(modelClass: Class) = + with(modelClass) { + when { + isAssignableFrom(LocationViewModel::class.java) -> + LocationViewModel(locationRepository) + else -> + throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") + } + } as T +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..6348baa --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/btn_background.xml b/app/src/main/res/drawable/btn_background.xml new file mode 100644 index 0000000..d80cb28 --- /dev/null +++ b/app/src/main/res/drawable/btn_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file 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..a0ad202 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_location.xml b/app/src/main/res/layout/activity_location.xml new file mode 100644 index 0000000..0931966 --- /dev/null +++ b/app/src/main/res/layout/activity_location.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_receiver.xml b/app/src/main/res/layout/fragment_receiver.xml new file mode 100644 index 0000000..724adde --- /dev/null +++ b/app/src/main/res/layout/fragment_receiver.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_transmitter.xml b/app/src/main/res/layout/fragment_transmitter.xml new file mode 100644 index 0000000..dc13f6f --- /dev/null +++ b/app/src/main/res/layout/fragment_transmitter.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + +