├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── kotlinc.xml └── misc.xml ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── pe │ │ └── pcs │ │ └── apirestunsplash │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── api_cuenta.png │ │ ├── api_info.png │ │ ├── api_login.png │ │ ├── estructura_app.png │ │ ├── pantalla_01.png │ │ └── respuesta_json.png │ ├── java │ │ └── pe │ │ │ └── pcs │ │ │ └── apirestunsplash │ │ │ ├── ApiUnsplashApp.kt │ │ │ ├── data │ │ │ ├── common │ │ │ │ ├── Constants.kt │ │ │ │ └── HeaderInterceptor.kt │ │ │ ├── local │ │ │ │ ├── dao │ │ │ │ │ ├── PhotoDao.kt │ │ │ │ │ └── UserDao.kt │ │ │ │ ├── database │ │ │ │ │ └── AppDatabase.kt │ │ │ │ └── entity │ │ │ │ │ ├── PhotoAndUrls.kt │ │ │ │ │ ├── PhotoEntity.kt │ │ │ │ │ ├── UrlsEntity.kt │ │ │ │ │ └── UserEntity.kt │ │ │ ├── remote │ │ │ │ ├── api │ │ │ │ │ └── UnsplashApi.kt │ │ │ │ └── model │ │ │ │ │ ├── PhotoModel.kt │ │ │ │ │ └── UrlsModel.kt │ │ │ └── repository │ │ │ │ ├── UnsplashRepositoryImpl.kt │ │ │ │ └── UserRepositoryImp.kt │ │ │ ├── di │ │ │ └── InjectionModule.kt │ │ │ ├── domain │ │ │ ├── model │ │ │ │ ├── Photo.kt │ │ │ │ ├── Urls.kt │ │ │ │ └── User.kt │ │ │ ├── repository │ │ │ │ ├── UnsplashRepository.kt │ │ │ │ └── UserRepository.kt │ │ │ └── usecase │ │ │ │ ├── CreateUserUseCase.kt │ │ │ │ ├── LoginUseCase.kt │ │ │ │ └── getListUnsplashUseCase.kt │ │ │ └── presentation │ │ │ ├── common │ │ │ ├── MakeCall.kt │ │ │ ├── ResponseState.kt │ │ │ ├── UtilsCommon.kt │ │ │ ├── UtilsDate.kt │ │ │ ├── UtilsMessage.kt │ │ │ └── UtilsSecurity.kt │ │ │ └── ui │ │ │ ├── CrearCuentaActivity.kt │ │ │ ├── CrearCuentaViewModel.kt │ │ │ ├── HomeFragment.kt │ │ │ ├── HomeViewModel.kt │ │ │ ├── InfoFragment.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── LoginViewModel.kt │ │ │ ├── MainActivity.kt │ │ │ └── PhotoAdapter.kt │ └── res │ │ ├── color │ │ └── bottom_nav_selector.xml │ │ ├── drawable │ │ ├── api_24px.xml │ │ ├── baseline_home_24.xml │ │ ├── baseline_info_24.xml │ │ ├── bottom_nav_selector.xml │ │ ├── ic_back.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ └── menu_redondeado.xml │ │ ├── font │ │ └── playball_regular.ttf │ │ ├── layout │ │ ├── activity_crear_cuenta.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── fragment_home.xml │ │ ├── fragment_info.xml │ │ └── items_result.xml │ │ ├── menu │ │ └── bottom_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── main_graph.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── pe │ └── pcs │ └── apirestunsplash │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Consumo de la Api Unsplash usando Retrofit, ROOM, patrón MVVM e inyección de dependencia con Dagger Hilt 2 | Mediante este ejemplo accederemos a la api de Unsplash, la particularidad de este endpoint es que la api_key esta al final de la misma url, para ello haremos uso los Interceptor para agregar dicha api_key. El resultado obtenido de la api será almacenado en ROOM. 3 | 4 | ## Requisitos 5 | 6 | - Android Studio Iguana | 2023.2.1 o superior. 7 | - Android Gradle Plugin Version 8.3.0 8 | - Gradle Version 8.4 9 | - Kotlin 1.9.22 o superior. 10 | - Api key, para ello deberá de ingresar a la página (https://unsplash.com/developers) y registrarse para obtener dicha api key. 11 | 12 | ## Dependencias 13 | 14 | - Retrofit: Para el consumo de la api. 15 | - ViewModel y LiveData: Para la implementación del patrón MVVM. 16 | - Dagger Hilt: Para la inyección de dependencias. 17 | - Coil: Sera usado para cargar las imagenes. 18 | - ROOM: Para almacenar la info recibida de la api. 19 | 20 | ## Estructura del proyecto 21 | 22 | - di: Contiene las inyeccion de dependencia a nivel de módulo. 23 | - data: Contiene las clases, interfaces para el consumo de la api, manejo de room, implementacion del repositorio, etc. 24 | - domain: Contiene el modelo, repositorio y el use case. 25 | - presentation: Contiene la interfaz de usuario, funcionalidades comunes, adaptador y el viewmodel. 26 | 27 | ## Resultado del endpoint 28 | De todo el resultado obtenido del endpoint, estos serán los campos que manejaremos. 29 | 30 | ![Image text](https://github.com/programadorescs/ApiRestUnsplash/blob/master/app/src/main/assets/respuesta_json.png) 31 | 32 | ## Estructura de la app 33 | ![Image text](https://github.com/programadorescs/ApiRestUnsplash/blob/master/app/src/main/assets/estructura_app.png) 34 | 35 | ## Imagen de la app 36 | 37 | ### Login 38 | ![Image text](https://github.com/programadorescs/ApiRestUnsplash/blob/master/app/src/main/assets/api_login.png) 39 | 40 | ### Registrar una nueva cuenta 41 | ![Image text](https://github.com/programadorescs/ApiRestUnsplash/blob/master/app/src/main/assets/api_cuenta.png) 42 | 43 | ### Datos de la api 44 | ![Image text](https://github.com/programadorescs/ApiRestUnsplash/blob/master/app/src/main/assets/pantalla_01.png) 45 | 46 | ### Info de la app 47 | ![Image text](https://github.com/programadorescs/ApiRestUnsplash/blob/master/app/src/main/assets/api_info.png) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | 5 | // Para dagger hilt 6 | id("kotlin-kapt") 7 | id("dagger.hilt.android.plugin") 8 | 9 | // Para las anotaciones (Room) 10 | id("com.google.devtools.ksp") 11 | } 12 | 13 | android { 14 | namespace = "pe.pcs.apirestunsplash" 15 | compileSdk = 34 16 | 17 | defaultConfig { 18 | applicationId = "pe.pcs.apirestunsplash" 19 | minSdk = 24 20 | targetSdk = 34 21 | versionCode = 11 22 | versionName = "1.0.10" 23 | 24 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 25 | } 26 | 27 | buildTypes { 28 | release { 29 | isMinifyEnabled = false 30 | proguardFiles( 31 | getDefaultProguardFile("proguard-android-optimize.txt"), 32 | "proguard-rules.pro" 33 | ) 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_1_8 38 | targetCompatibility = JavaVersion.VERSION_1_8 39 | } 40 | kotlinOptions { 41 | jvmTarget = "1.8" 42 | } 43 | buildFeatures { 44 | viewBinding = true 45 | } 46 | } 47 | 48 | dependencies { 49 | 50 | implementation("androidx.core:core-ktx:1.12.0") 51 | implementation("androidx.appcompat:appcompat:1.6.1") 52 | implementation("com.google.android.material:material:1.11.0") 53 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") 54 | 55 | implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") 56 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") 57 | implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") 58 | implementation("androidx.navigation:navigation-ui-ktx:2.7.7") 59 | 60 | // Retrofit 61 | implementation("com.squareup.retrofit2:retrofit:2.9.0") 62 | implementation("com.squareup.retrofit2:converter-gson:2.9.0") 63 | implementation("com.squareup.okhttp3:logging-interceptor:4.3.1") 64 | implementation("com.google.code.gson:gson:2.10.1") 65 | 66 | //Room 67 | implementation("androidx.room:room-ktx:2.6.1") 68 | ksp("androidx.room:room-compiler:2.6.1") 69 | 70 | // Dagger - Hilt 71 | implementation("com.google.dagger:hilt-android:2.51") 72 | kapt("com.google.dagger:hilt-compiler:2.51") 73 | 74 | // Coil - cargar imagenes 75 | implementation("io.coil-kt:coil:2.5.0") 76 | 77 | testImplementation("junit:junit:4.13.2") 78 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 79 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 80 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/pe/pcs/apirestunsplash/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("pe.pcs.apirestunsplash", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 19 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/assets/api_cuenta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/assets/api_cuenta.png -------------------------------------------------------------------------------- /app/src/main/assets/api_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/assets/api_info.png -------------------------------------------------------------------------------- /app/src/main/assets/api_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/assets/api_login.png -------------------------------------------------------------------------------- /app/src/main/assets/estructura_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/assets/estructura_app.png -------------------------------------------------------------------------------- /app/src/main/assets/pantalla_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/assets/pantalla_01.png -------------------------------------------------------------------------------- /app/src/main/assets/respuesta_json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/assets/respuesta_json.png -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/ApiUnsplashApp.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import dagger.hilt.android.HiltAndroidApp 6 | 7 | @HiltAndroidApp 8 | class ApiUnsplashApp : Application() { 9 | 10 | companion object { 11 | private var instancia: ApiUnsplashApp? = null 12 | 13 | fun getContext(): Context { 14 | return instancia!!.applicationContext 15 | } 16 | } 17 | 18 | init { 19 | instancia = this 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/common/Constants.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.common 2 | 3 | object Constants { 4 | const val URL_BASE = "https://api.unsplash.com/" 5 | const val API_KEY = "AQUI_DEBES_INGRESAR_TU_API_KEY" 6 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/common/HeaderInterceptor.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.common 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | import javax.inject.Inject 6 | import javax.inject.Singleton 7 | 8 | @Singleton 9 | class HeaderInterceptor @Inject constructor() : Interceptor { 10 | 11 | override fun intercept(chain: Interceptor.Chain): Response { 12 | 13 | val request = chain.request() 14 | // Obtiene la url original (https://api.unsplash.com/photos/?), antes de agregar la api key 15 | val originalUrl = request.url 16 | 17 | // Crea la nueva url (https://api.unsplash.com/photos/?&client_id=AQUI_ESTARA_TU_API_KEY), 18 | // tomando los datos de la url original mas los datos de la api key 19 | val newUrl = originalUrl.newBuilder() 20 | .addQueryParameter("client_id", Constants.API_KEY) 21 | .build() 22 | 23 | // Finalmente, construye la nueva url indicando el metodo, en este caso el GET y la url 24 | val newRequest = request.newBuilder() 25 | .url(newUrl) 26 | .build() 27 | 28 | return chain.proceed(newRequest) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/dao/PhotoDao.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import androidx.room.Transaction 7 | import pe.pcs.apirestunsplash.data.local.entity.PhotoAndUrls 8 | import pe.pcs.apirestunsplash.data.local.entity.PhotoEntity 9 | import pe.pcs.apirestunsplash.data.local.entity.UrlsEntity 10 | 11 | @Dao 12 | interface PhotoDao { 13 | 14 | @Query("SELECT Count(id) FROM photo") 15 | suspend fun countPhoto(): Int 16 | 17 | @Insert 18 | suspend fun insert(entity: PhotoEntity) 19 | 20 | @Insert 21 | suspend fun insertUrl(entity: UrlsEntity) 22 | 23 | @Query("DELETE FROM photo") 24 | suspend fun deleteAll() 25 | 26 | @Query("DELETE FROM urls") 27 | suspend fun deleteUrlAll() 28 | 29 | @Transaction 30 | suspend fun insertPhotoWithUrl(photo: PhotoEntity, url: UrlsEntity) { 31 | insert(photo) 32 | url.idPhoto = photo.id 33 | insertUrl(url) 34 | } 35 | 36 | @Transaction 37 | suspend fun insertPhotosWithUrls(photos: List) { 38 | for (photoWithUrl in photos) { 39 | insert(photoWithUrl.photo) 40 | 41 | photoWithUrl.urls.idPhoto = photoWithUrl.photo.id 42 | insertUrl(photoWithUrl.urls) 43 | } 44 | } 45 | 46 | @Transaction 47 | @Query("SELECT * FROM photo") 48 | suspend fun getPhotoAndUrls(): List 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/dao/UserDao.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import androidx.room.Transaction 7 | import pe.pcs.apirestunsplash.data.local.entity.UserEntity 8 | 9 | @Dao 10 | interface UserDao { 11 | @Insert 12 | suspend fun create(entity: UserEntity): Long 13 | 14 | @Query("Select id, name, nickname, password From user Where nickname=:nickname") 15 | suspend fun verifyNickname(nickname: String): UserEntity? 16 | 17 | @Query("Select id, name, nickname, password From user Where nickname=:nickname AND password=:password") 18 | suspend fun login(nickname: String, password: String): UserEntity? 19 | 20 | @Transaction 21 | suspend fun createTransaction(entity: UserEntity): Long { 22 | if(verifyNickname(entity.nickname) != null) 23 | throw Exception("El nickname ya existe en la db") 24 | 25 | return create(entity) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import pe.pcs.apirestunsplash.data.local.dao.PhotoDao 6 | import pe.pcs.apirestunsplash.data.local.dao.UserDao 7 | import pe.pcs.apirestunsplash.data.local.entity.PhotoEntity 8 | import pe.pcs.apirestunsplash.data.local.entity.UrlsEntity 9 | import pe.pcs.apirestunsplash.data.local.entity.UserEntity 10 | 11 | 12 | @Database( 13 | entities = [ 14 | PhotoEntity::class, 15 | UrlsEntity::class, 16 | UserEntity::class 17 | ], 18 | version = 1, 19 | exportSchema = false 20 | ) 21 | abstract class AppDatabase: RoomDatabase() { 22 | 23 | abstract fun photoDao(): PhotoDao 24 | 25 | abstract fun userDao(): UserDao 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/entity/PhotoAndUrls.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.entity 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Relation 5 | 6 | data class PhotoAndUrls( 7 | @Embedded val photo: PhotoEntity, 8 | @Relation( 9 | parentColumn = "id", 10 | entityColumn = "idphoto" 11 | ) 12 | val urls: UrlsEntity 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/entity/PhotoEntity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import pe.pcs.apirestunsplash.data.remote.model.PhotoModel 7 | 8 | @Entity( 9 | tableName = "photo" 10 | ) 11 | data class PhotoEntity( 12 | @PrimaryKey 13 | @ColumnInfo("id") var id: String = "", 14 | @ColumnInfo("createdAt") var createdAt: String = "", 15 | @ColumnInfo("updatedAt") var updatedAt: String = "", 16 | @ColumnInfo("description") var description: String? = "", 17 | @ColumnInfo("likes") val likes: Int = 0 18 | ) 19 | 20 | fun PhotoModel.toDatabase() = PhotoEntity( 21 | id = id, 22 | createdAt = created_at, 23 | updatedAt = updated_at, 24 | description = description, 25 | likes = likes 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/entity/UrlsEntity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import pe.pcs.apirestunsplash.data.remote.model.UrlsModel 7 | 8 | @Entity( 9 | tableName = "urls" 10 | ) 11 | data class UrlsEntity( 12 | @PrimaryKey(autoGenerate = true) 13 | @ColumnInfo(name = "id") var id: Int = 0, 14 | @ColumnInfo("idphoto") var idPhoto: String = "", 15 | @ColumnInfo("regular") val regular: String = "", 16 | @ColumnInfo("small") val small: String = "" 17 | ) 18 | 19 | fun UrlsModel.toDatabase() = UrlsEntity( 20 | regular = regular, 21 | small = small 22 | ) -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/local/entity/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.local.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | import pe.pcs.apirestunsplash.domain.model.User 8 | 9 | @Entity( 10 | tableName = "user", 11 | indices = [Index(value = ["nickname"], unique = true)] 12 | ) 13 | data class UserEntity( 14 | @PrimaryKey(autoGenerate = true) 15 | @ColumnInfo("id") var id: Int = 0, 16 | @ColumnInfo("name") var name: String = "", 17 | @ColumnInfo("nickname") var nickname: String = "", 18 | @ColumnInfo("password") var password: String = "" 19 | ) 20 | 21 | fun User.toDatabase() = UserEntity( 22 | id = id, 23 | name = name, 24 | nickname = nickname, 25 | password = password 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/remote/api/UnsplashApi.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.remote.api 2 | 3 | import pe.pcs.apirestunsplash.data.remote.model.PhotoModel 4 | import retrofit2.http.GET 5 | 6 | interface UnsplashApi { 7 | 8 | @GET("photos/?") 9 | suspend fun getPhotos(): List 10 | 11 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/remote/model/PhotoModel.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.remote.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class PhotoModel( 6 | @SerializedName("id") var id: String = "", 7 | @SerializedName("created_at") var created_at: String = "", 8 | @SerializedName("updated_at") var updated_at: String = "", 9 | @SerializedName("alt_description") var description: String? = "", 10 | @SerializedName("likes") var likes: Int, 11 | @SerializedName("urls") var urls: UrlsModel? = UrlsModel() 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/remote/model/UrlsModel.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.remote.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class UrlsModel( 6 | @SerializedName("regular") val regular: String="", 7 | @SerializedName("small") val small: String="" 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/repository/UnsplashRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.repository 2 | 3 | import pe.pcs.apirestunsplash.data.local.dao.PhotoDao 4 | import pe.pcs.apirestunsplash.data.local.entity.PhotoAndUrls 5 | import pe.pcs.apirestunsplash.data.local.entity.toDatabase 6 | import pe.pcs.apirestunsplash.data.remote.api.UnsplashApi 7 | import pe.pcs.apirestunsplash.domain.model.Photo 8 | import pe.pcs.apirestunsplash.domain.model.toDomain 9 | import pe.pcs.apirestunsplash.domain.repository.UnsplashRepository 10 | import javax.inject.Inject 11 | 12 | class UnsplashRepositoryImpl @Inject constructor( 13 | private val api: UnsplashApi, 14 | private val dao: PhotoDao 15 | ) : UnsplashRepository { 16 | 17 | override suspend fun getList(): List { 18 | //dao.deleteUrlAll() 19 | //dao.deleteAll() 20 | 21 | if (dao.countPhoto() == 0) { 22 | val listPhotoAndUrls = mutableListOf() 23 | 24 | api.getPhotos().forEach { 25 | listPhotoAndUrls.add( 26 | PhotoAndUrls( 27 | it.toDatabase(), 28 | it.urls!!.toDatabase() 29 | ) 30 | ) 31 | } 32 | 33 | dao.insertPhotosWithUrls(listPhotoAndUrls) 34 | } 35 | 36 | val listPhoto = mutableListOf() 37 | 38 | // Obtener todas las fotos con sus URLs 39 | dao.getPhotoAndUrls().forEach { 40 | val entity = it.photo.toDomain() 41 | entity.urls = it.urls.toDomain() 42 | listPhoto.add(entity) 43 | } 44 | 45 | return listPhoto 46 | } 47 | 48 | override suspend fun getUrlList(): List { 49 | // Usado para trabajar unicamente con la api (sin almacenar los datos en room) 50 | return api.getPhotos().map { 51 | it.toDomain() 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/data/repository/UserRepositoryImp.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.data.repository 2 | 3 | import pe.pcs.apirestunsplash.data.local.dao.UserDao 4 | import pe.pcs.apirestunsplash.data.local.entity.toDatabase 5 | import pe.pcs.apirestunsplash.domain.model.User 6 | import pe.pcs.apirestunsplash.domain.model.toDomain 7 | import pe.pcs.apirestunsplash.domain.repository.UserRepository 8 | import javax.inject.Inject 9 | 10 | class UserRepositoryImp @Inject constructor( 11 | private val dao: UserDao 12 | ): UserRepository { 13 | override suspend fun login(username: String, password: String): User? { 14 | return dao.login(username, password)?.toDomain() 15 | } 16 | 17 | override suspend fun create(user: User): Long { 18 | return dao.createTransaction(user.toDatabase()) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/di/InjectionModule.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import okhttp3.OkHttpClient 11 | import okhttp3.logging.HttpLoggingInterceptor 12 | import pe.pcs.apirestunsplash.data.common.Constants 13 | import pe.pcs.apirestunsplash.data.local.dao.PhotoDao 14 | import pe.pcs.apirestunsplash.data.local.dao.UserDao 15 | import pe.pcs.apirestunsplash.data.local.database.AppDatabase 16 | import pe.pcs.apirestunsplash.data.remote.api.UnsplashApi 17 | import pe.pcs.apirestunsplash.data.repository.UnsplashRepositoryImpl 18 | import pe.pcs.apirestunsplash.data.repository.UserRepositoryImp 19 | import pe.pcs.apirestunsplash.data.common.HeaderInterceptor 20 | import pe.pcs.apirestunsplash.domain.repository.UnsplashRepository 21 | import pe.pcs.apirestunsplash.domain.repository.UserRepository 22 | import retrofit2.Retrofit 23 | import retrofit2.converter.gson.GsonConverterFactory 24 | import javax.inject.Singleton 25 | 26 | @Module 27 | @InstallIn(SingletonComponent::class) 28 | object InjectionModule { 29 | 30 | //******** Inyectando RETROFIT ********// 31 | 32 | @Singleton 33 | @Provides 34 | fun provideOkHttpClient(headerInterceptor: HeaderInterceptor): OkHttpClient { 35 | return OkHttpClient 36 | .Builder() 37 | .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 38 | .addInterceptor(headerInterceptor) 39 | .build() 40 | } 41 | 42 | @Singleton 43 | @Provides 44 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { 45 | return Retrofit.Builder() 46 | .baseUrl(Constants.URL_BASE) 47 | .client(okHttpClient) 48 | .addConverterFactory(GsonConverterFactory.create()) 49 | .build() 50 | } 51 | 52 | @Singleton 53 | @Provides 54 | fun provideUnplashApi(retrofit: Retrofit): UnsplashApi { 55 | return retrofit.create(UnsplashApi::class.java) 56 | } 57 | 58 | @Singleton 59 | @Provides 60 | fun provideUnplashRepository(api: UnsplashApi, dao: PhotoDao): UnsplashRepository { 61 | return UnsplashRepositoryImpl(api, dao) 62 | } 63 | 64 | //******** Inyectando ROOM ********// 65 | 66 | private const val DATABASE_NAME = "photo_db" 67 | 68 | @Singleton 69 | @Provides 70 | fun provideRoom(@ApplicationContext context: Context): AppDatabase { 71 | return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME).build() 72 | } 73 | 74 | @Singleton 75 | @Provides 76 | fun providePhotoDao(db: AppDatabase): PhotoDao { 77 | return db.photoDao() 78 | } 79 | 80 | @Singleton 81 | @Provides 82 | fun provideUserDao(db: AppDatabase): UserDao { 83 | return db.userDao() 84 | } 85 | 86 | @Singleton 87 | @Provides 88 | fun provideUserRepository(dao: UserDao): UserRepository { 89 | return UserRepositoryImp(dao) 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/model/Photo.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.model 2 | 3 | import pe.pcs.apirestunsplash.data.local.entity.PhotoEntity 4 | import pe.pcs.apirestunsplash.data.remote.model.PhotoModel 5 | 6 | data class Photo( 7 | val id: String = "", 8 | val createdAt: String = "", 9 | val updatedAt: String = "", 10 | val description: String? = "", 11 | val likes: Int, 12 | var urls: Urls? = Urls() 13 | ) 14 | 15 | fun PhotoModel.toDomain() = Photo( 16 | id = id, 17 | createdAt = created_at, 18 | updatedAt = updated_at, 19 | description = description, 20 | likes = likes, 21 | urls = urls?.toDomain() 22 | ) 23 | 24 | fun PhotoEntity.toDomain() = Photo( 25 | id = id, 26 | createdAt = createdAt, 27 | updatedAt = updatedAt, 28 | description = description, 29 | likes = likes 30 | ) -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/model/Urls.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.model 2 | 3 | import pe.pcs.apirestunsplash.data.local.entity.UrlsEntity 4 | import pe.pcs.apirestunsplash.data.remote.model.UrlsModel 5 | 6 | data class Urls( 7 | val regular: String = "", 8 | val small: String = "" 9 | ) 10 | 11 | fun UrlsModel.toDomain() = Urls( 12 | regular = regular, 13 | small = small 14 | ) 15 | 16 | fun UrlsEntity.toDomain() = Urls( 17 | regular = regular, 18 | small = small 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/model/User.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.model 2 | 3 | import pe.pcs.apirestunsplash.data.local.entity.UserEntity 4 | 5 | data class User( 6 | var id: Int = 0, 7 | var name: String = "", 8 | var nickname: String = "", 9 | var password: String = "" 10 | ) 11 | 12 | fun UserEntity.toDomain() = User( 13 | id = id, 14 | name = name, 15 | nickname = nickname, 16 | password = password 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/repository/UnsplashRepository.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.repository 2 | 3 | import pe.pcs.apirestunsplash.domain.model.Photo 4 | 5 | interface UnsplashRepository { 6 | 7 | suspend fun getList(): List 8 | 9 | suspend fun getUrlList(): List 10 | 11 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.repository 2 | 3 | import pe.pcs.apirestunsplash.domain.model.User 4 | 5 | interface UserRepository { 6 | suspend fun login(username: String, password: String): User? 7 | 8 | suspend fun create(user: User): Long 9 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/usecase/CreateUserUseCase.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.usecase 2 | 3 | import pe.pcs.apirestunsplash.domain.model.User 4 | import pe.pcs.apirestunsplash.domain.repository.UserRepository 5 | import javax.inject.Inject 6 | 7 | class CreateUserUseCase @Inject constructor( 8 | private val repository: UserRepository 9 | ) { 10 | suspend operator fun invoke(user: User): Int { 11 | return repository.create(user).toInt() 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/usecase/LoginUseCase.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.usecase 2 | 3 | import pe.pcs.apirestunsplash.domain.model.User 4 | import pe.pcs.apirestunsplash.domain.repository.UserRepository 5 | import javax.inject.Inject 6 | 7 | class LoginUseCase @Inject constructor( 8 | private val repository: UserRepository 9 | ) { 10 | suspend operator fun invoke(nickname: String, password: String): User? { 11 | return repository.login(nickname, password) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/domain/usecase/getListUnsplashUseCase.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.domain.usecase 2 | 3 | import pe.pcs.apirestunsplash.domain.model.Photo 4 | import pe.pcs.apirestunsplash.domain.repository.UnsplashRepository 5 | import javax.inject.Inject 6 | 7 | class getListUnsplashUseCase @Inject constructor( 8 | private val repository: UnsplashRepository 9 | ) { 10 | 11 | suspend operator fun invoke(): List { 12 | return repository.getList() 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/common/MakeCall.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.common 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import retrofit2.HttpException 6 | import java.net.UnknownHostException 7 | 8 | suspend fun makeCall( 9 | call: suspend () -> T 10 | ): ResponseState { 11 | return withContext(Dispatchers.IO) { 12 | try { 13 | ResponseState.Success(call()) 14 | } catch (e: UnknownHostException) { 15 | // UnknownHostException -> Error de internet o red 16 | ResponseState.Error(e.message.toString()) 17 | } catch (e: HttpException) { 18 | ResponseState.Error(e.message.toString()) 19 | } catch (e: Exception) { 20 | ResponseState.Error(e.message.toString()) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/common/ResponseState.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.common 2 | 3 | sealed class ResponseState { 4 | class Loading : ResponseState() 5 | class Success(val data: T) : ResponseState() 6 | class Error(val message: String) : ResponseState() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/common/UtilsCommon.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.common 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.inputmethod.InputMethodManager 6 | import pe.pcs.apirestunsplash.ApiUnsplashApp 7 | 8 | object UtilsCommon { 9 | fun hideKeyboard(view: View) { 10 | val imm = ApiUnsplashApp.getContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 11 | imm.hideSoftInputFromWindow(view.windowToken, 0) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/common/UtilsDate.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.common 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.Date 5 | import java.util.Locale 6 | 7 | object UtilsDate { 8 | 9 | fun formatearFecha(fecha: Date): String { 10 | return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(fecha) 11 | } 12 | 13 | fun formatearFecha(fecha: String): String { 14 | val date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT).parse(fecha) 15 | return SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(date!!) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/common/UtilsMessage.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.common 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 6 | import pe.pcs.apirestunsplash.ApiUnsplashApp 7 | 8 | object UtilsMessage { 9 | 10 | fun showAlertOk(titulo: String?, mensaje: String?, contexto: Context) { 11 | val builder = MaterialAlertDialogBuilder(contexto) 12 | builder.setMessage(mensaje) 13 | .setTitle(titulo) 14 | .setCancelable(false) 15 | .setPositiveButton("Aceptar") { dialog, _ -> dialog.cancel() } 16 | builder.create().show() 17 | } 18 | 19 | fun showToast(mensaje: String) { 20 | Toast.makeText( 21 | ApiUnsplashApp.getContext(), 22 | mensaje, 23 | Toast.LENGTH_LONG 24 | ).show() 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/common/UtilsSecurity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.common 2 | 3 | import android.util.Base64 4 | import java.security.MessageDigest 5 | 6 | object UtilsSecurity { 7 | fun createSHAHash512(cadena: String): String { 8 | return try { 9 | val digest = MessageDigest.getInstance("SHA-512") 10 | val hash = digest.digest(cadena.toByteArray(charset("UTF-8"))) 11 | Base64.encodeToString(hash, 0).replace("[\n\r]".toRegex(), "") 12 | } catch (ex: Exception) { 13 | throw RuntimeException(ex) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/CrearCuentaActivity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import androidx.activity.viewModels 6 | import androidx.core.view.isVisible 7 | import dagger.hilt.android.AndroidEntryPoint 8 | import pe.pcs.apirestunsplash.databinding.ActivityCrearCuentaBinding 9 | import pe.pcs.apirestunsplash.domain.model.User 10 | import pe.pcs.apirestunsplash.presentation.common.ResponseState 11 | import pe.pcs.apirestunsplash.presentation.common.UtilsCommon 12 | import pe.pcs.apirestunsplash.presentation.common.UtilsMessage 13 | import pe.pcs.apirestunsplash.presentation.common.UtilsSecurity 14 | 15 | @AndroidEntryPoint 16 | class CrearCuentaActivity : AppCompatActivity() { 17 | 18 | private lateinit var binding: ActivityCrearCuentaBinding 19 | private val viewModel: CrearCuentaViewModel by viewModels() 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | binding = ActivityCrearCuentaBinding.inflate(layoutInflater) 24 | setContentView(binding.root) 25 | 26 | initListener() 27 | initUiState() 28 | } 29 | 30 | private fun initListener() { 31 | binding.toolbar.setNavigationOnClickListener { 32 | onBackPressedDispatcher.onBackPressed() 33 | } 34 | 35 | binding.btRegister.setOnClickListener { 36 | UtilsCommon.hideKeyboard(it) 37 | 38 | if (binding.etName.text.toString().trim().isEmpty() || 39 | binding.etNickname.text.toString().trim().isEmpty() || 40 | binding.etPassword.text.toString().trim().isEmpty() 41 | ) { 42 | UtilsMessage.showAlertOk( 43 | "ADVERTENCIA", 44 | "Todos los datos son necesarios", 45 | this 46 | ) 47 | return@setOnClickListener 48 | } 49 | 50 | viewModel.setCreateUser( 51 | User().apply { 52 | name = binding.etName.text.toString().trim() 53 | nickname = binding.etNickname.text.toString().trim() 54 | password = UtilsSecurity.createSHAHash512(binding.etPassword.text.toString().trim()) 55 | } 56 | ) 57 | } 58 | } 59 | 60 | private fun initUiState() { 61 | viewModel.uiState.observe(this) { 62 | when (it) { 63 | is ResponseState.Error -> { 64 | binding.progressBar.isVisible = false 65 | UtilsMessage.showAlertOk("ERROR", it.message, this) 66 | } 67 | 68 | is ResponseState.Loading -> binding.progressBar.isVisible = true 69 | is ResponseState.Success -> { 70 | binding.progressBar.isVisible = false 71 | android.util.Log.e("JACK", it.data.toString()) 72 | if (it.data < 1) return@observe 73 | 74 | UtilsMessage.showAlertOk( 75 | "EXITO", 76 | "Cuenta creada correctamente, vuelva a login para acceder con su credencial", 77 | this 78 | ) 79 | 80 | binding.etName.setText("") 81 | binding.etNickname.setText("") 82 | binding.etPassword.setText("") 83 | } 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/CrearCuentaViewModel.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.launch 9 | import pe.pcs.apirestunsplash.domain.model.User 10 | import pe.pcs.apirestunsplash.domain.usecase.CreateUserUseCase 11 | import pe.pcs.apirestunsplash.presentation.common.ResponseState 12 | import pe.pcs.apirestunsplash.presentation.common.makeCall 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class CrearCuentaViewModel @Inject constructor( 17 | private val createUserUseCase: CreateUserUseCase 18 | ) : ViewModel() { 19 | private val _uiState = MutableLiveData>() 20 | val uiState: LiveData> = _uiState 21 | 22 | fun setCreateUser(user: User) { 23 | viewModelScope.launch { 24 | _uiState.value = ResponseState.Loading() 25 | 26 | makeCall { 27 | createUserUseCase(user) 28 | }.let { 29 | _uiState.value = it 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.viewModels 9 | import androidx.core.view.isVisible 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import pe.pcs.apirestunsplash.databinding.FragmentHomeBinding 13 | import pe.pcs.apirestunsplash.presentation.common.ResponseState 14 | import pe.pcs.apirestunsplash.presentation.common.UtilsMessage 15 | 16 | @AndroidEntryPoint 17 | class HomeFragment : Fragment() { 18 | 19 | private lateinit var binding: FragmentHomeBinding 20 | private val viewModel: HomeViewModel by viewModels() 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View? { 26 | binding = FragmentHomeBinding.inflate(inflater, container, false) 27 | return binding.root 28 | } 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | 33 | initListener() 34 | initUiState() 35 | 36 | viewModel.getAllPhoto() 37 | } 38 | 39 | private fun errorMessage(msg: String) { 40 | binding.progressBar.isVisible = false 41 | UtilsMessage.showAlertOk("ERROR", msg, requireContext()) 42 | } 43 | 44 | private fun initListener() { 45 | binding.rvLista.apply { 46 | layoutManager = LinearLayoutManager(requireContext()) 47 | adapter = PhotoAdapter() 48 | } 49 | } 50 | 51 | private fun initUiState() { 52 | viewModel.listPhoto.observe(viewLifecycleOwner) { 53 | (binding.rvLista.adapter as PhotoAdapter).submitList(it) 54 | } 55 | 56 | viewModel.stateList.observe(viewLifecycleOwner) { 57 | when (it) { 58 | is ResponseState.Error -> errorMessage(it.message) 59 | is ResponseState.Loading -> binding.progressBar.isVisible = true 60 | is ResponseState.Success -> binding.progressBar.isVisible = false 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.launch 9 | import pe.pcs.apirestunsplash.presentation.common.ResponseState 10 | import pe.pcs.apirestunsplash.presentation.common.makeCall 11 | import pe.pcs.apirestunsplash.domain.model.Photo 12 | import pe.pcs.apirestunsplash.domain.usecase.getListUnsplashUseCase 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class HomeViewModel @Inject constructor( 17 | private val getListUnsplashUseCase: getListUnsplashUseCase 18 | ) : ViewModel() { 19 | 20 | private val _listPhoto = MutableLiveData?>() 21 | val listPhoto: LiveData?> = _listPhoto 22 | 23 | private val _stateList = MutableLiveData>>() 24 | val stateList: LiveData>> = _stateList 25 | 26 | fun getAllPhoto() { 27 | viewModelScope.launch { 28 | _stateList.value = ResponseState.Loading() 29 | 30 | makeCall { 31 | getListUnsplashUseCase() 32 | }.let { 33 | if (it is ResponseState.Success) 34 | _listPhoto.value = it.data 35 | 36 | _stateList.value = it 37 | } 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/InfoFragment.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import pe.pcs.apirestunsplash.databinding.FragmentInfoBinding 9 | 10 | 11 | class InfoFragment : Fragment() { 12 | 13 | private lateinit var binding: FragmentInfoBinding 14 | 15 | override fun onCreateView( 16 | inflater: LayoutInflater, container: ViewGroup?, 17 | savedInstanceState: Bundle? 18 | ): View? { 19 | binding = FragmentInfoBinding.inflate(layoutInflater) 20 | return binding.root 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import androidx.activity.viewModels 7 | import androidx.core.view.isVisible 8 | import dagger.hilt.android.AndroidEntryPoint 9 | import pe.pcs.apirestunsplash.databinding.ActivityLoginBinding 10 | import pe.pcs.apirestunsplash.presentation.common.ResponseState 11 | import pe.pcs.apirestunsplash.presentation.common.UtilsCommon 12 | import pe.pcs.apirestunsplash.presentation.common.UtilsMessage 13 | import pe.pcs.apirestunsplash.presentation.common.UtilsSecurity 14 | 15 | @AndroidEntryPoint 16 | class LoginActivity : AppCompatActivity() { 17 | 18 | private lateinit var binding: ActivityLoginBinding 19 | private val viewModel: LoginViewModel by viewModels() 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | binding = ActivityLoginBinding.inflate(layoutInflater) 24 | setContentView(binding.root) 25 | 26 | initListener() 27 | initUiState() 28 | } 29 | 30 | private fun initListener() { 31 | binding.btAcceder.setOnClickListener { 32 | UtilsCommon.hideKeyboard(it) 33 | 34 | if (binding.etNickname.text.toString().trim().isEmpty() || 35 | binding.etPassword.text.toString().trim().isEmpty() 36 | ) { 37 | UtilsMessage.showToast("Faltan datos de acceso") 38 | return@setOnClickListener 39 | } 40 | 41 | viewModel.getLogin( 42 | binding.etNickname.text.toString().trim(), 43 | UtilsSecurity.createSHAHash512(binding.etPassword.text.toString().trim()), 44 | ) 45 | } 46 | 47 | binding.tvCrearCuenta.setOnClickListener { 48 | startActivity(Intent(this, CrearCuentaActivity::class.java)) 49 | } 50 | } 51 | 52 | private fun initUiState() { 53 | viewModel.uiState.observe(this) { 54 | when (it) { 55 | is ResponseState.Error -> { 56 | binding.progressBar.isVisible = false 57 | UtilsMessage.showAlertOk("ERROR", it.message, this) 58 | } 59 | 60 | is ResponseState.Loading -> binding.progressBar.isVisible = true 61 | is ResponseState.Success -> { 62 | binding.progressBar.isVisible = false 63 | 64 | if (it.data == null) { 65 | UtilsMessage.showAlertOk( 66 | "ERROR", 67 | "Las credenciales de acceso no son correctos", 68 | this 69 | ) 70 | return@observe 71 | } 72 | 73 | startActivity(Intent(this, MainActivity::class.java)) 74 | finish() 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.launch 9 | import pe.pcs.apirestunsplash.domain.model.User 10 | import pe.pcs.apirestunsplash.domain.usecase.LoginUseCase 11 | import pe.pcs.apirestunsplash.presentation.common.ResponseState 12 | import pe.pcs.apirestunsplash.presentation.common.makeCall 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class LoginViewModel @Inject constructor( 17 | private val loginUseCase: LoginUseCase 18 | ) : ViewModel() { 19 | private val _uiState = MutableLiveData>() 20 | val uiState: LiveData> = _uiState 21 | 22 | fun getLogin(nickname: String, password: String) { 23 | viewModelScope.launch { 24 | _uiState.value = ResponseState.Loading() 25 | 26 | makeCall { 27 | loginUseCase(nickname, password) 28 | }.let { 29 | _uiState.value = it 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.core.content.ContextCompat 6 | import androidx.navigation.NavController 7 | import androidx.navigation.fragment.NavHostFragment 8 | import androidx.navigation.ui.setupWithNavController 9 | import dagger.hilt.android.AndroidEntryPoint 10 | import pe.pcs.apirestunsplash.R 11 | import pe.pcs.apirestunsplash.databinding.ActivityMainBinding 12 | 13 | @AndroidEntryPoint 14 | class MainActivity : AppCompatActivity() { 15 | 16 | private lateinit var binding: ActivityMainBinding 17 | private lateinit var navController: NavController 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | binding = ActivityMainBinding.inflate(layoutInflater) 22 | setContentView(binding.root) 23 | 24 | window.navigationBarColor = ContextCompat.getColor(this, R.color.primaryDark) 25 | 26 | initNavigation() 27 | } 28 | 29 | private fun initNavigation() { 30 | val navHost = 31 | supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment 32 | navController = navHost.navController 33 | binding.bottomNavView.setupWithNavController(navController) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/pe/pcs/apirestunsplash/presentation/ui/PhotoAdapter.kt: -------------------------------------------------------------------------------- 1 | package pe.pcs.apirestunsplash.presentation.ui 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import coil.load 9 | import coil.transform.CircleCropTransformation 10 | import pe.pcs.apirestunsplash.databinding.ItemsResultBinding 11 | import pe.pcs.apirestunsplash.domain.model.Photo 12 | import pe.pcs.apirestunsplash.presentation.common.UtilsDate 13 | 14 | class PhotoAdapter() : ListAdapter(DiffCallback) { 15 | 16 | private object DiffCallback : DiffUtil.ItemCallback() { 17 | override fun areItemsTheSame(oldItem: Photo, newItem: Photo): Boolean { 18 | return oldItem.id == newItem.id 19 | } 20 | 21 | override fun areContentsTheSame(oldItem: Photo, newItem: Photo): Boolean { 22 | return oldItem == newItem 23 | } 24 | } 25 | 26 | inner class BindViewHolder(private val binding: ItemsResultBinding) : 27 | RecyclerView.ViewHolder(binding.root) { 28 | fun enlazar(entidad: Photo) { 29 | binding.imageView.load(entidad.urls?.regular) { 30 | crossfade(true) 31 | transformations(CircleCropTransformation()) 32 | } 33 | binding.tvDescripcion.text = entidad.description 34 | binding.tvLike.text = "Likes: ${entidad.likes.toString()}" 35 | binding.tvDate.text = UtilsDate.formatearFecha(entidad.createdAt) 36 | } 37 | } 38 | 39 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder { 40 | return BindViewHolder( 41 | ItemsResultBinding.inflate(LayoutInflater.from(parent.context), parent, false) 42 | ) 43 | } 44 | 45 | override fun onBindViewHolder(holder: BindViewHolder, position: Int) { 46 | holder.enlazar( 47 | getItem(position) 48 | ) 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/res/color/bottom_nav_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/api_24px.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_info_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_nav_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/menu_redondeado.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/font/playball_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/programadorescs/ApiRestUnsplash/fb2f133b69a03bd6433aa2ebe257c258a196fae2/app/src/main/res/font/playball_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_crear_cuenta.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 26 | 27 | 30 | 31 | 41 | 42 | 47 | 48 | 49 | 59 | 60 | 66 | 67 | 68 | 79 | 80 | 86 | 87 | 88 |