diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..289565fa
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+build
+/captures
+.externalNativeBuild
+.cxx
+*.class
+bin/
+gen/
+local.properties
+local.gradle
+release/
+staging/
+debug/
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..b646e064
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,91 @@
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+ id 'kotlin-kapt'
+ id "androidx.navigation.safeargs.kotlin"
+ id 'dagger.hilt.android.plugin'
+}
+
+android {
+ compileSdkVersion 31
+ buildToolsVersion "31.0.0"
+
+ defaultConfig {
+ applicationId "br.com.example.starwars"
+ minSdkVersion 21
+ targetSdkVersion 31
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ dataBinding {
+ enabled = true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ buildConfigField "String", "API_ENDPOINT", "\"$rootProject.ext.apiEndpoint/\""
+ }
+
+ debug {
+ buildConfigField "String", "API_ENDPOINT", "\"$rootProject.ext.apiEndpoint/\""
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ // Kotlin
+ implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+ implementation "androidx.core:core-ktx:$kotlinCoreVersion"
+
+ // Navigation
+ implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
+ implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
+
+ // LifeCycle
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifeCycleVersion"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$viewModelVersion"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$viewModelVersion"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$liveDataVersion"
+ implementation "androidx.fragment:fragment-ktx:1.4.0"
+
+ // Hilt
+ implementation "com.google.dagger:hilt-android:$hiltVersion"
+ kapt "com.google.dagger:hilt-android-compiler:$hiltVersion"
+
+ // Retrofit
+ implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
+ api "com.squareup.retrofit2:converter-gson:$retrofitVersion"
+
+ // Room
+ implementation "androidx.room:room-runtime:$roomVersion"
+ kapt "androidx.room:room-compiler:$roomVersion"
+ implementation "androidx.room:room-ktx:$roomVersion"
+ implementation "androidx.room:room-paging:2.4.0"
+
+ // Android
+ implementation "androidx.appcompat:appcompat:$appcompatVersion"
+ implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion"
+ implementation "com.google.android.material:material:$materialVersion"
+
+ // Paging
+ implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
+ testImplementation "androidx.paging:paging-common-ktx:$pagingVersion"
+
+ // Tests
+ testImplementation "junit:junit:$junitVersion"
+ androidTestImplementation "androidx.test.ext:junit:$junitExtVersion"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /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
\ No newline at end of file
diff --git a/app/src/androidTest/java/br/com/example/starwars/ExampleInstrumentedTest.kt b/app/src/androidTest/java/br/com/example/starwars/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..89c0c0c7
--- /dev/null
+++ b/app/src/androidTest/java/br/com/example/starwars/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package br.com.example.starwars
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext
+ assertEquals("br.com.example.starwars", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a7ef0325
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/entities/ApiListPeopleResponse.kt b/app/src/main/java/br/com/example/starwars/data/entities/ApiListPeopleResponse.kt
new file mode 100644
index 00000000..cb95737b
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/entities/ApiListPeopleResponse.kt
@@ -0,0 +1,11 @@
+package br.com.example.starwars.data.entities
+
+
+import com.google.gson.annotations.SerializedName
+
+data class ApiListPeopleResponse(
+ @SerializedName("count")
+ val count: Int? = null,
+ @SerializedName("results")
+ val people: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/entities/ApiPeople.kt b/app/src/main/java/br/com/example/starwars/data/entities/ApiPeople.kt
new file mode 100644
index 00000000..5635dd92
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/entities/ApiPeople.kt
@@ -0,0 +1,55 @@
+package br.com.example.starwars.data.entities
+
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import br.com.example.starwars.domain.entities.People
+import com.google.gson.annotations.SerializedName
+
+@Entity(tableName = "people")
+data class ApiPeople(
+ @PrimaryKey(autoGenerate = true)
+ val id: Int?,
+ @SerializedName("gender")
+ val gender: String? = null,
+ @SerializedName("height")
+ val height: String? = null,
+ @SerializedName("mass")
+ val mass: String? = null,
+ @SerializedName("name")
+ val name: String? = null,
+ @SerializedName("url")
+ val url: String? = null,
+ @SerializedName("hair_color")
+ val hairColor: String? = null,
+ @SerializedName("skin_color")
+ val skinColor: String? = null,
+ @SerializedName("eye_color")
+ val eyeColor: String? = null,
+ @SerializedName("birth_year")
+ val birthYear: String? = null,
+ @SerializedName("homeworld")
+ val homeWorld: String? = null,
+ @SerializedName("species")
+ val species: List? = null,
+ val favorite: Boolean = false
+) {
+
+ fun apiPeopleToPeople(apiPeople: ApiPeople): People {
+ return People(
+ id = apiPeople.id,
+ name = apiPeople.name,
+ height = apiPeople.height,
+ gender = apiPeople.gender,
+ mass = apiPeople.mass,
+ url = apiPeople.url,
+ hairColor = apiPeople.hairColor,
+ skinColor = apiPeople.skinColor,
+ eyeColor = apiPeople.eyeColor,
+ birthYear = apiPeople.birthYear,
+ homeWorld = apiPeople.homeWorld,
+ species = apiPeople.species,
+ favorite = apiPeople.favorite
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/entities/ApiPlanet.kt b/app/src/main/java/br/com/example/starwars/data/entities/ApiPlanet.kt
new file mode 100644
index 00000000..fd7c335b
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/entities/ApiPlanet.kt
@@ -0,0 +1,15 @@
+package br.com.example.starwars.data.entities
+
+import br.com.example.starwars.domain.entities.Planet
+import com.google.gson.annotations.SerializedName
+
+data class ApiPlanet(
+ @SerializedName("name")
+ val name: String? = null
+) {
+ fun apiPlanetToPlanet(apiPlanet: ApiPlanet): Planet {
+ return Planet(
+ name = apiPlanet.name
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/entities/ApiSpecie.kt b/app/src/main/java/br/com/example/starwars/data/entities/ApiSpecie.kt
new file mode 100644
index 00000000..7c338e11
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/entities/ApiSpecie.kt
@@ -0,0 +1,15 @@
+package br.com.example.starwars.data.entities
+
+import br.com.example.starwars.domain.entities.Specie
+import com.google.gson.annotations.SerializedName
+
+data class ApiSpecie(
+ @SerializedName("name")
+ val name: String? = null
+) {
+ fun apiSpecieToSpecie(apiSpecie: ApiSpecie): Specie {
+ return Specie(
+ name = apiSpecie.name
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/entities/RemoteKeys.kt b/app/src/main/java/br/com/example/starwars/data/entities/RemoteKeys.kt
new file mode 100644
index 00000000..dde67d7c
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/entities/RemoteKeys.kt
@@ -0,0 +1,12 @@
+package br.com.example.starwars.data.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "remote_keys")
+data class RemoteKeys(
+ @PrimaryKey(autoGenerate = true)
+ val repoId: Long? = 0,
+ val prevKey: Int?,
+ val nextKey: Int?
+)
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/local/AppDataBase.kt b/app/src/main/java/br/com/example/starwars/data/local/AppDataBase.kt
new file mode 100644
index 00000000..d98a1604
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/local/AppDataBase.kt
@@ -0,0 +1,29 @@
+package br.com.example.starwars.data.local
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import br.com.example.starwars.data.entities.ApiPeople
+import br.com.example.starwars.data.entities.RemoteKeys
+import br.com.example.starwars.data.util.StringListConverter
+
+@Database(entities = [ApiPeople::class, RemoteKeys::class], version = 1, exportSchema = false)
+@TypeConverters(StringListConverter::class)
+abstract class AppDataBase : RoomDatabase() {
+
+ abstract fun peopleDao(): PeopleDao
+ abstract fun remoteKeysDao(): RemoteKeysDao
+
+ companion object {
+ private const val DATABASE_NAME = "star-wars-db"
+
+ fun build(context: Context): AppDataBase {
+ return Room.databaseBuilder(
+ context,
+ AppDataBase::class.java, DATABASE_NAME
+ ).build()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/local/PeopleDao.kt b/app/src/main/java/br/com/example/starwars/data/local/PeopleDao.kt
new file mode 100644
index 00000000..e9d56b68
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/local/PeopleDao.kt
@@ -0,0 +1,24 @@
+package br.com.example.starwars.data.local
+
+import androidx.paging.PagingSource
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import br.com.example.starwars.data.entities.ApiPeople
+
+@Dao
+interface PeopleDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertAll(apiPeople: List)
+
+ @Query("SELECT * FROM people")
+ fun getListPeople(): PagingSource
+
+ @Query("UPDATE people SET favorite = :favorite WHERE id = :id")
+ suspend fun updatePerson(favorite: Boolean, id: Int)
+
+ @Query("DELETE FROM people")
+ suspend fun clearPeople()
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/local/RemoteKeysDao.kt b/app/src/main/java/br/com/example/starwars/data/local/RemoteKeysDao.kt
new file mode 100644
index 00000000..3dd517ef
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/local/RemoteKeysDao.kt
@@ -0,0 +1,19 @@
+package br.com.example.starwars.data.local
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import br.com.example.starwars.data.entities.RemoteKeys
+
+@Dao
+interface RemoteKeysDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(remoteKey: RemoteKeys)
+
+ @Query("SELECT * FROM remote_keys WHERE repoId = :repoId")
+ suspend fun remoteKeysRepoId(repoId: Long): RemoteKeys?
+
+ @Query("DELETE FROM remote_keys")
+ suspend fun clearRemoteKeys()
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/remote/ApiService.kt b/app/src/main/java/br/com/example/starwars/data/remote/ApiService.kt
new file mode 100644
index 00000000..58c8a5b0
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/remote/ApiService.kt
@@ -0,0 +1,20 @@
+package br.com.example.starwars.data.remote
+
+import br.com.example.starwars.data.entities.ApiListPeopleResponse
+import br.com.example.starwars.data.entities.ApiPlanet
+import br.com.example.starwars.data.entities.ApiSpecie
+import retrofit2.http.GET
+import retrofit2.http.Query
+import retrofit2.http.Url
+
+interface ApiService {
+
+ @GET("people")
+ suspend fun getListPeople(@Query("page") page: Int): ApiListPeopleResponse
+
+ @GET
+ suspend fun getPlanet(@Url url: String): ApiPlanet
+
+ @GET
+ suspend fun getSpecie(@Url url: String): ApiSpecie
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/repository/FavoriteRepositoryImpl.kt b/app/src/main/java/br/com/example/starwars/data/repository/FavoriteRepositoryImpl.kt
new file mode 100644
index 00000000..ae39ffdc
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/repository/FavoriteRepositoryImpl.kt
@@ -0,0 +1,20 @@
+package br.com.example.starwars.data.repository
+
+import br.com.example.starwars.data.local.PeopleDao
+import br.com.example.starwars.data.remote.ApiService
+import br.com.example.starwars.domain.repository.FavoriteRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class FavoriteRepositoryImpl @Inject constructor(
+ private val peopleDao: PeopleDao,
+ private val apiService: ApiService
+) : FavoriteRepository {
+
+ override suspend fun addAndRemoveFavorite(favorite: Boolean, id: Int) {
+ withContext(Dispatchers.IO) {
+ peopleDao.updatePerson(favorite, id)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/repository/GetPeopleListRepositoryImpl.kt b/app/src/main/java/br/com/example/starwars/data/repository/GetPeopleListRepositoryImpl.kt
new file mode 100644
index 00000000..02fddb02
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/repository/GetPeopleListRepositoryImpl.kt
@@ -0,0 +1,30 @@
+package br.com.example.starwars.data.repository
+
+import androidx.paging.*
+import br.com.example.starwars.data.local.AppDataBase
+import br.com.example.starwars.data.remote.ApiService
+import br.com.example.starwars.data.repository.paging.PeoplePagingSource
+import br.com.example.starwars.data.repository.paging.PeopleRemoteMediator
+import br.com.example.starwars.domain.entities.People
+import br.com.example.starwars.domain.repository.GetPeopleListRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class GetPeopleListRepositoryImpl @Inject constructor(
+ private val apiService: ApiService,
+ private val dataBase: AppDataBase
+) : GetPeopleListRepository {
+
+ @ExperimentalPagingApi
+ override suspend fun getPeopleList(): Flow> {
+ return Pager(
+ PagingConfig(pageSize = 10, enablePlaceholders = false, prefetchDistance = 3),
+ remoteMediator = PeopleRemoteMediator(apiService, dataBase),
+ pagingSourceFactory = { PeoplePagingSource(apiService) }
+ ).flow
+ .map { people ->
+ people.map { it.apiPeopleToPeople(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/repository/GetPlanetRepositoryImpl.kt b/app/src/main/java/br/com/example/starwars/data/repository/GetPlanetRepositoryImpl.kt
new file mode 100644
index 00000000..79327fca
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/repository/GetPlanetRepositoryImpl.kt
@@ -0,0 +1,18 @@
+package br.com.example.starwars.data.repository
+
+import br.com.example.starwars.data.remote.ApiService
+import br.com.example.starwars.domain.entities.Planet
+import br.com.example.starwars.domain.repository.GetPlanetRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class GetPlanetRepositoryImpl @Inject constructor(
+ private val apiService: ApiService
+) : GetPlanetRepository {
+ override suspend fun getPlanet(url: String): Planet {
+ return withContext(Dispatchers.IO) {
+ apiService.getPlanet(url).let { it.apiPlanetToPlanet(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/repository/GetSpecieRepositoryImpl.kt b/app/src/main/java/br/com/example/starwars/data/repository/GetSpecieRepositoryImpl.kt
new file mode 100644
index 00000000..1c4de240
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/repository/GetSpecieRepositoryImpl.kt
@@ -0,0 +1,18 @@
+package br.com.example.starwars.data.repository
+
+import br.com.example.starwars.data.remote.ApiService
+import br.com.example.starwars.domain.entities.Specie
+import br.com.example.starwars.domain.repository.GetSpecieRepository
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+class GetSpecieRepositoryImpl @Inject constructor(
+ private val apiService: ApiService
+) : GetSpecieRepository {
+ override suspend fun getSpecie(url: String): Specie {
+ return withContext(Dispatchers.IO) {
+ apiService.getSpecie(url).let { it.apiSpecieToSpecie(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/repository/paging/PeoplePagingSource.kt b/app/src/main/java/br/com/example/starwars/data/repository/paging/PeoplePagingSource.kt
new file mode 100644
index 00000000..11cccf11
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/repository/paging/PeoplePagingSource.kt
@@ -0,0 +1,40 @@
+package br.com.example.starwars.data.repository.paging
+
+import androidx.paging.PagingSource
+import androidx.paging.PagingState
+import br.com.example.starwars.data.entities.ApiPeople
+import br.com.example.starwars.data.remote.ApiService
+import retrofit2.HttpException
+import java.io.IOException
+
+class PeoplePagingSource(
+ private val apiService: ApiService
+) : PagingSource() {
+ override suspend fun load(params: LoadParams): LoadResult {
+ return try {
+ val position = params.key ?: PAGE_INDEX
+ val response = apiService.getListPeople(page = position)
+
+ LoadResult.Page(
+ response.people,
+ if (position == PAGE_INDEX) null else position - 1,
+ position.plus(1)
+ )
+ } catch (exception: IOException) {
+ return LoadResult.Error(exception)
+ } catch (exception: HttpException) {
+ return LoadResult.Error(exception)
+ }
+ }
+
+ override fun getRefreshKey(state: PagingState): Int? {
+ return state.anchorPosition?.let { anchorPosition ->
+ state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
+ ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
+ }
+ }
+
+ companion object {
+ private const val PAGE_INDEX = 1
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/repository/paging/PeopleRemoteMediator.kt b/app/src/main/java/br/com/example/starwars/data/repository/paging/PeopleRemoteMediator.kt
new file mode 100644
index 00000000..c3144cca
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/repository/paging/PeopleRemoteMediator.kt
@@ -0,0 +1,100 @@
+package br.com.example.starwars.data.repository.paging
+
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.LoadType
+import androidx.paging.PagingState
+import androidx.paging.RemoteMediator
+import androidx.room.withTransaction
+import br.com.example.starwars.data.entities.ApiPeople
+import br.com.example.starwars.data.entities.RemoteKeys
+import br.com.example.starwars.data.local.AppDataBase
+import br.com.example.starwars.data.remote.ApiService
+import retrofit2.HttpException
+import java.io.IOException
+
+@OptIn(ExperimentalPagingApi::class)
+class PeopleRemoteMediator(
+ private val apiService: ApiService,
+ private val dataBase: AppDataBase
+) : RemoteMediator() {
+ override suspend fun load(
+ loadType: LoadType,
+ state: PagingState
+ ): MediatorResult {
+
+ val page = when (loadType) {
+ LoadType.REFRESH -> {
+ val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
+ remoteKeys?.nextKey?.minus(1) ?: PAGE_INDEX
+ }
+ LoadType.PREPEND -> {
+ val remoteKeys = getRemoteKeyForFirstItem(state)
+ val prevKey = remoteKeys?.prevKey
+ if (prevKey == null) {
+ return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
+ }
+ prevKey
+ }
+ LoadType.APPEND -> {
+ val remoteKeys = getRemoteKeyForLastItem(state)
+ val nextKey = remoteKeys?.nextKey
+ if (nextKey == null) {
+ return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
+ }
+ nextKey
+ }
+ }
+
+ return try {
+ val response = apiService.getListPeople(page = page)
+ val endOfPaginationReached = response.people.isEmpty()
+
+ dataBase.withTransaction {
+ if (loadType == LoadType.REFRESH) {
+ dataBase.remoteKeysDao().clearRemoteKeys()
+ dataBase.peopleDao().clearPeople()
+ }
+ val prevKey = if (page == PAGE_INDEX) null else page - 1
+ val nextKey = if (endOfPaginationReached) null else page + 1
+
+ val key = RemoteKeys(prevKey = prevKey, nextKey = nextKey)
+ dataBase.remoteKeysDao().insert(key)
+ dataBase.peopleDao().insertAll(response.people)
+ }
+
+ return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
+ } catch (exception: IOException) {
+ MediatorResult.Error(exception)
+ } catch (exception: HttpException) {
+ MediatorResult.Error(exception)
+ }
+ }
+
+ private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? {
+ return state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()
+ ?.let { repo ->
+ repo.id?.toLong()?.let { dataBase.remoteKeysDao().remoteKeysRepoId(it) }
+ }
+ }
+
+ private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? {
+ return state.pages.firstOrNull() { it.data.isNotEmpty() }?.data?.firstOrNull()
+ ?.let { repo ->
+ repo.id?.toLong()?.let { dataBase.remoteKeysDao().remoteKeysRepoId(it) }
+ }
+ }
+
+ private suspend fun getRemoteKeyClosestToCurrentPosition(
+ state: PagingState
+ ): RemoteKeys? {
+ return state.anchorPosition?.let { position ->
+ state.closestItemToPosition(position)?.id?.let { repoId ->
+ dataBase.remoteKeysDao().remoteKeysRepoId(repoId.toLong())
+ }
+ }
+ }
+
+ companion object {
+ private const val PAGE_INDEX = 1
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/data/util/StringListConverter.kt b/app/src/main/java/br/com/example/starwars/data/util/StringListConverter.kt
new file mode 100644
index 00000000..562fc492
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/data/util/StringListConverter.kt
@@ -0,0 +1,25 @@
+package br.com.example.starwars.data.util
+
+import androidx.room.TypeConverter
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+
+
+class StringListConverter {
+
+ private val gson: Gson = Gson()
+
+ @TypeConverter
+ fun jsonToObject(data: String): List {
+ if (data.isNullOrEmpty())
+ return listOf()
+
+ val listType = object : TypeToken>() {}.type
+ return gson.fromJson(data, listType)
+ }
+
+ @TypeConverter
+ fun objectToJson(data: List): String {
+ return gson.toJson(data)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/entities/People.kt b/app/src/main/java/br/com/example/starwars/domain/entities/People.kt
new file mode 100644
index 00000000..53f444b6
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/entities/People.kt
@@ -0,0 +1,19 @@
+package br.com.example.starwars.domain.entities
+
+import java.io.Serializable
+
+data class People(
+ val id: Int? = null,
+ val gender: String? = null,
+ val height: String? = null,
+ val mass: String? = null,
+ val name: String? = null,
+ val url: String? = null,
+ val hairColor: String? = null,
+ val skinColor: String? = null,
+ val eyeColor: String? = null,
+ val birthYear: String? = null,
+ val homeWorld: String? = null,
+ val species: List? = null,
+ var favorite: Boolean = false
+) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/entities/Planet.kt b/app/src/main/java/br/com/example/starwars/domain/entities/Planet.kt
new file mode 100644
index 00000000..2d6b62c4
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/entities/Planet.kt
@@ -0,0 +1,5 @@
+package br.com.example.starwars.domain.entities
+
+data class Planet(
+ val name: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/entities/Specie.kt b/app/src/main/java/br/com/example/starwars/domain/entities/Specie.kt
new file mode 100644
index 00000000..0c69592a
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/entities/Specie.kt
@@ -0,0 +1,5 @@
+package br.com.example.starwars.domain.entities
+
+data class Specie(
+ val name: String? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/repository/FavoriteRepository.kt b/app/src/main/java/br/com/example/starwars/domain/repository/FavoriteRepository.kt
new file mode 100644
index 00000000..1c2f2324
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/repository/FavoriteRepository.kt
@@ -0,0 +1,5 @@
+package br.com.example.starwars.domain.repository
+
+interface FavoriteRepository {
+ suspend fun addAndRemoveFavorite(favorite: Boolean, id: Int)
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/repository/GetPeopleListRepository.kt b/app/src/main/java/br/com/example/starwars/domain/repository/GetPeopleListRepository.kt
new file mode 100644
index 00000000..e3c5b941
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/repository/GetPeopleListRepository.kt
@@ -0,0 +1,11 @@
+package br.com.example.starwars.domain.repository
+
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.PagingData
+import br.com.example.starwars.domain.entities.People
+import kotlinx.coroutines.flow.Flow
+
+interface GetPeopleListRepository {
+ @ExperimentalPagingApi
+ suspend fun getPeopleList(): Flow>
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/repository/GetPlanetRepository.kt b/app/src/main/java/br/com/example/starwars/domain/repository/GetPlanetRepository.kt
new file mode 100644
index 00000000..c2163ffc
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/repository/GetPlanetRepository.kt
@@ -0,0 +1,7 @@
+package br.com.example.starwars.domain.repository
+
+import br.com.example.starwars.domain.entities.Planet
+
+interface GetPlanetRepository {
+ suspend fun getPlanet(url: String): Planet
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/repository/GetSpecieRepository.kt b/app/src/main/java/br/com/example/starwars/domain/repository/GetSpecieRepository.kt
new file mode 100644
index 00000000..2ef46254
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/repository/GetSpecieRepository.kt
@@ -0,0 +1,7 @@
+package br.com.example.starwars.domain.repository
+
+import br.com.example.starwars.domain.entities.Specie
+
+interface GetSpecieRepository {
+ suspend fun getSpecie(url: String): Specie
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/usecase/FavoritePerson.kt b/app/src/main/java/br/com/example/starwars/domain/usecase/FavoritePerson.kt
new file mode 100644
index 00000000..85b2383f
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/usecase/FavoritePerson.kt
@@ -0,0 +1,10 @@
+package br.com.example.starwars.domain.usecase
+
+import br.com.example.starwars.domain.repository.FavoriteRepository
+import javax.inject.Inject
+
+class FavoritePerson @Inject constructor(
+ private val repository: FavoriteRepository
+) {
+ suspend fun execute(favorite: Boolean, id: Int) = repository.addAndRemoveFavorite(favorite, id)
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/usecase/GetPeopleList.kt b/app/src/main/java/br/com/example/starwars/domain/usecase/GetPeopleList.kt
new file mode 100644
index 00000000..9e5bffae
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/usecase/GetPeopleList.kt
@@ -0,0 +1,12 @@
+package br.com.example.starwars.domain.usecase
+
+import androidx.paging.ExperimentalPagingApi
+import br.com.example.starwars.domain.repository.GetPeopleListRepository
+import javax.inject.Inject
+
+class GetPeopleList @Inject constructor(
+ private val repository: GetPeopleListRepository
+) {
+ @ExperimentalPagingApi
+ suspend fun execute() = repository.getPeopleList()
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/usecase/GetPlanet.kt b/app/src/main/java/br/com/example/starwars/domain/usecase/GetPlanet.kt
new file mode 100644
index 00000000..0a3559a5
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/usecase/GetPlanet.kt
@@ -0,0 +1,11 @@
+package br.com.example.starwars.domain.usecase
+
+import br.com.example.starwars.domain.repository.GetPlanetRepository
+import javax.inject.Inject
+
+class GetPlanet @Inject constructor(
+ private val repository: GetPlanetRepository
+) {
+
+ suspend fun execute(url: String) = repository.getPlanet(url)
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/domain/usecase/GetSpecie.kt b/app/src/main/java/br/com/example/starwars/domain/usecase/GetSpecie.kt
new file mode 100644
index 00000000..925e7413
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/domain/usecase/GetSpecie.kt
@@ -0,0 +1,11 @@
+package br.com.example.starwars.domain.usecase
+
+import br.com.example.starwars.domain.repository.GetSpecieRepository
+import javax.inject.Inject
+
+class GetSpecie @Inject constructor(
+ private val repository: GetSpecieRepository
+) {
+
+ suspend fun execute(url: String) = repository.getSpecie(url)
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ApplicationStarWars.kt b/app/src/main/java/br/com/example/starwars/presentation/ApplicationStarWars.kt
new file mode 100644
index 00000000..0d4b7fcf
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ApplicationStarWars.kt
@@ -0,0 +1,7 @@
+package br.com.example.starwars.presentation
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class ApplicationStarWars : Application()
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/di/ApiProviderModule.kt b/app/src/main/java/br/com/example/starwars/presentation/di/ApiProviderModule.kt
new file mode 100644
index 00000000..daa2d1e9
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/di/ApiProviderModule.kt
@@ -0,0 +1,32 @@
+package br.com.example.starwars.presentation.di
+
+import br.com.example.starwars.BuildConfig.API_ENDPOINT
+import br.com.example.starwars.data.remote.ApiService
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ApiProviderModule {
+
+ @Singleton
+ @Provides
+ fun providesRetrofit(): Retrofit {
+ return Retrofit
+ .Builder()
+ .addConverterFactory(GsonConverterFactory.create())
+ .baseUrl(API_ENDPOINT)
+ .build()
+ }
+
+ @Singleton
+ @Provides
+ fun providesApiService(retrofit: Retrofit): ApiService {
+ return retrofit.create(ApiService::class.java)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/di/ApplicationBindingModule.kt b/app/src/main/java/br/com/example/starwars/presentation/di/ApplicationBindingModule.kt
new file mode 100644
index 00000000..fa48d2a1
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/di/ApplicationBindingModule.kt
@@ -0,0 +1,39 @@
+package br.com.example.starwars.presentation.di
+
+import br.com.example.starwars.data.repository.FavoriteRepositoryImpl
+import br.com.example.starwars.data.repository.GetPeopleListRepositoryImpl
+import br.com.example.starwars.data.repository.GetPlanetRepositoryImpl
+import br.com.example.starwars.data.repository.GetSpecieRepositoryImpl
+import br.com.example.starwars.domain.repository.FavoriteRepository
+import br.com.example.starwars.domain.repository.GetPeopleListRepository
+import br.com.example.starwars.domain.repository.GetPlanetRepository
+import br.com.example.starwars.domain.repository.GetSpecieRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface ApplicationBindingModule {
+
+ @Binds
+ fun bindGetPeopleListRepository(
+ repository: GetPeopleListRepositoryImpl
+ ): GetPeopleListRepository
+
+ @Binds
+ fun bindGetPlanetRepository(
+ repository: GetPlanetRepositoryImpl
+ ): GetPlanetRepository
+
+ @Binds
+ fun bindGetSpecieRepository(
+ repository: GetSpecieRepositoryImpl
+ ): GetSpecieRepository
+
+ @Binds
+ fun bindFavoriteRepository(
+ repository: FavoriteRepositoryImpl
+ ): FavoriteRepository
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/di/DataBaseModule.kt b/app/src/main/java/br/com/example/starwars/presentation/di/DataBaseModule.kt
new file mode 100644
index 00000000..a383ce5f
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/di/DataBaseModule.kt
@@ -0,0 +1,27 @@
+package br.com.example.starwars.presentation.di
+
+import android.content.Context
+import br.com.example.starwars.data.local.AppDataBase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class DataBaseModule {
+
+ @Provides
+ @Singleton
+ fun provideDatabase(@ApplicationContext context: Context) = AppDataBase.build(context)
+
+ @Provides
+ @Singleton
+ fun providePeopleDao(appDataBase: AppDataBase) = appDataBase.peopleDao()
+
+ @Provides
+ @Singleton
+ fun provideRemoteKeysDao(appDataBase: AppDataBase) = appDataBase.remoteKeysDao()
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/MainActivity.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/MainActivity.kt
new file mode 100644
index 00000000..1a84ee4a
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/MainActivity.kt
@@ -0,0 +1,19 @@
+package br.com.example.starwars.presentation.ui
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.databinding.DataBindingUtil
+import br.com.example.starwars.R
+import br.com.example.starwars.databinding.ActivityMainBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleAdapter.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleAdapter.kt
new file mode 100644
index 00000000..8bfe1bbb
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleAdapter.kt
@@ -0,0 +1,38 @@
+package br.com.example.starwars.presentation.ui.listpeople
+
+import android.view.ViewGroup
+import androidx.paging.PagingDataAdapter
+import androidx.recyclerview.widget.DiffUtil
+import br.com.example.starwars.domain.entities.People
+
+class ListPeopleAdapter(
+ private val callbackClick: (People) -> Unit,
+ private val callbackFavorite: (People) -> Unit
+) : PagingDataAdapter(DIFF_CALLBACK) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListPeopleViewHolder {
+ return ListPeopleViewHolder.inflate(parent, callbackClick, callbackFavorite)
+ }
+
+ override fun onBindViewHolder(holder: ListPeopleViewHolder, position: Int) {
+ getItem(position)?.let { holder.bind(it) }
+ }
+
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: People,
+ newItem: People
+ ): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(
+ oldItem: People,
+ newItem: People
+ ): Boolean {
+ return oldItem == newItem
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleFragment.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleFragment.kt
new file mode 100644
index 00000000..ae1903e0
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleFragment.kt
@@ -0,0 +1,97 @@
+package br.com.example.starwars.presentation.ui.listpeople
+
+import android.os.Bundle
+import android.view.*
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.NavDirections
+import androidx.navigation.fragment.findNavController
+import androidx.paging.ExperimentalPagingApi
+import androidx.recyclerview.widget.LinearLayoutManager
+import br.com.example.starwars.R
+import br.com.example.starwars.databinding.FragmentListPeopleBinding
+import br.com.example.starwars.domain.entities.People
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@ExperimentalPagingApi
+@AndroidEntryPoint
+class ListPeopleFragment : Fragment() {
+
+ private lateinit var binding: FragmentListPeopleBinding
+ private val viewModel: ListPeopleViewModel by viewModels()
+ private val adapterList: ListPeopleAdapter =
+ ListPeopleAdapter(::callbackClick, ::callbackFavorite)
+ val controller by lazy { findNavController() }
+ lateinit var direction: NavDirections
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ super.onCreateView(inflater, container, savedInstanceState)
+ binding = FragmentListPeopleBinding.inflate(inflater, container, false)
+ setHasOptionsMenu(true)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupRecycler()
+ listAll()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.menu_filter, menu)
+ super.onCreateOptionsMenu(menu, inflater)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.list_all -> {
+ listAll()
+ }
+ R.id.list_favorites -> {
+ listFavorites()
+ }
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun listAll() {
+ lifecycleScope.launch {
+ viewModel.getList().collectLatest {
+ adapterList.submitData(it)
+ }
+ }
+ }
+
+ private fun listFavorites() {
+ lifecycleScope.launch {
+ viewModel.listFavorites().collectLatest {
+ adapterList.submitData(it)
+ }
+ }
+ }
+
+ private fun callbackClick(people: People) {
+ direction =
+ ListPeopleFragmentDirections.actionListPeopleFragmentToPeopleDetailFragment(people)
+ controller.navigate(direction)
+ }
+
+ private fun callbackFavorite(people: People) {
+ viewModel.favoritePerson(people)
+ }
+
+ private fun setupRecycler() {
+ with(binding.recyclerList) {
+ adapter = adapterList
+ layoutManager = LinearLayoutManager(context)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleViewHolder.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleViewHolder.kt
new file mode 100644
index 00000000..c238683f
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleViewHolder.kt
@@ -0,0 +1,66 @@
+package br.com.example.starwars.presentation.ui.listpeople
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import br.com.example.starwars.R
+import br.com.example.starwars.databinding.ItemPeopleBinding
+import br.com.example.starwars.domain.entities.People
+
+class ListPeopleViewHolder(
+ private val binding: ItemPeopleBinding,
+ private val callbackClick: (People) -> Unit,
+ private val callbackFavorite: (People) -> Unit
+) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(people: People) {
+ with(binding) {
+ name.text = people.name
+ height.text = people.height
+ mass.text = people.mass
+ gender.text = people.gender
+ cardItem.setOnClickListener {
+ callbackClick.invoke(people)
+ }
+
+ if (!people.favorite) {
+ favorite.setImageResource(R.drawable.ic_favorite_border)
+ } else {
+ favorite.setImageResource(R.drawable.ic_favorite)
+ }
+ }
+ setupFavorite(people)
+ }
+
+ private fun setupFavorite(people: People) {
+ with(binding) {
+ favorite.setOnClickListener {
+ if (people.favorite) {
+ favorite.setImageResource(R.drawable.ic_favorite_border)
+ } else {
+ favorite.setImageResource(R.drawable.ic_favorite)
+ }
+ people.favorite = people.favorite.not()
+ callbackFavorite.invoke(people)
+ }
+ }
+ }
+
+ companion object {
+ fun inflate(
+ parent: ViewGroup,
+ callbackClick: (People) -> Unit,
+ callbackFavorite: (People) -> Unit
+ ): ListPeopleViewHolder {
+ return ListPeopleViewHolder(
+ ItemPeopleBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ ),
+ callbackClick,
+ callbackFavorite
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleViewModel.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleViewModel.kt
new file mode 100644
index 00000000..944e74d5
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/listpeople/ListPeopleViewModel.kt
@@ -0,0 +1,43 @@
+package br.com.example.starwars.presentation.ui.listpeople
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.PagingData
+import androidx.paging.cachedIn
+import androidx.paging.filter
+import br.com.example.starwars.domain.entities.People
+import br.com.example.starwars.domain.usecase.FavoritePerson
+import br.com.example.starwars.domain.usecase.GetPeopleList
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ListPeopleViewModel @Inject constructor(
+ private val getPeopleList: GetPeopleList,
+ private val favoritePerson: FavoritePerson
+) : ViewModel() {
+
+ @ExperimentalPagingApi
+ suspend fun getList(): Flow> {
+ return getPeopleList.execute().cachedIn(viewModelScope)
+ }
+
+ internal fun favoritePerson(people: People) {
+ viewModelScope.launch {
+ people.id?.let { favoritePerson.execute(people.favorite, it) }
+ }
+ }
+
+ @ExperimentalPagingApi
+ internal suspend fun listFavorites(): Flow> {
+ return getPeopleList.execute().map { paging ->
+ paging.filter {
+ it.favorite
+ }
+ }.cachedIn(viewModelScope)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/peopledetail/PeopleDetailFragment.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/peopledetail/PeopleDetailFragment.kt
new file mode 100644
index 00000000..0c9c1575
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/peopledetail/PeopleDetailFragment.kt
@@ -0,0 +1,79 @@
+package br.com.example.starwars.presentation.ui.peopledetail
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.navigation.fragment.navArgs
+import br.com.example.starwars.R
+import br.com.example.starwars.databinding.FragmentDetailPeopleBinding
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class PeopleDetailFragment : Fragment() {
+
+ private lateinit var binding: FragmentDetailPeopleBinding
+ private val viewModel: PeopleDetailViewModel by viewModels()
+ private val args by navArgs()
+ private val people by lazy { args.people }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ super.onCreateView(inflater, container, savedInstanceState)
+ binding = FragmentDetailPeopleBinding.inflate(inflater, container, false)
+
+ binding.people = people
+ subscribeUi()
+ setClickListeners()
+ alteredIcon()
+ return binding.root
+ }
+
+ private fun setClickListeners() {
+ with(binding) {
+ addFavorite.setOnClickListener {
+ this@PeopleDetailFragment.people.favorite =
+ this@PeopleDetailFragment.people.favorite.not()
+ viewModel.favoritePerson(this@PeopleDetailFragment.people)
+ alteredIcon()
+ }
+ }
+ }
+
+ private fun subscribeUi() {
+ people.homeWorld?.let { homeWorld ->
+ people.species?.let { specie ->
+ viewModel.getPlanetAndSpecie(homeWorld, specie)
+ }
+ }
+
+ with(viewModel) {
+ specie.observe(viewLifecycleOwner) {
+ binding.specie.text = it.name
+ }
+ planet.observe(viewLifecycleOwner) {
+ binding.homeWorld.text = it.name
+ }
+ loading.observe(viewLifecycleOwner) {
+ if (it) {
+ binding.includeLoading.visibility = View.VISIBLE
+ } else {
+ binding.includeLoading.visibility = View.GONE
+ }
+ }
+ }
+ }
+
+ private fun alteredIcon() {
+ if (people.favorite) {
+ binding.addFavorite.setImageResource(R.drawable.ic_favorite)
+ } else {
+ binding.addFavorite.setImageResource(R.drawable.ic_favorite_border)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/example/starwars/presentation/ui/peopledetail/PeopleDetailViewModel.kt b/app/src/main/java/br/com/example/starwars/presentation/ui/peopledetail/PeopleDetailViewModel.kt
new file mode 100644
index 00000000..c36caadf
--- /dev/null
+++ b/app/src/main/java/br/com/example/starwars/presentation/ui/peopledetail/PeopleDetailViewModel.kt
@@ -0,0 +1,53 @@
+package br.com.example.starwars.presentation.ui.peopledetail
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import br.com.example.starwars.domain.entities.People
+import br.com.example.starwars.domain.entities.Planet
+import br.com.example.starwars.domain.entities.Specie
+import br.com.example.starwars.domain.usecase.FavoritePerson
+import br.com.example.starwars.domain.usecase.GetPlanet
+import br.com.example.starwars.domain.usecase.GetSpecie
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class PeopleDetailViewModel @Inject constructor(
+ private val getPlanet: GetPlanet,
+ private val getSpecie: GetSpecie,
+ private val favoritePerson: FavoritePerson
+) : ViewModel() {
+
+ val planet: LiveData get() = _planet
+ private val _planet: MutableLiveData = MutableLiveData()
+
+ val specie: LiveData get() = _specie
+ private val _specie: MutableLiveData = MutableLiveData()
+
+ val loading: LiveData get() = _loading
+ private val _loading: MutableLiveData = MutableLiveData()
+
+ internal fun getPlanetAndSpecie(homeWorld: String, species: List) {
+ viewModelScope.launch {
+ awaitAll(
+ async { _planet.value = getPlanet.execute(homeWorld) },
+ async {
+ if (species.isNotEmpty())
+ _specie.value = getSpecie.execute(species[0])
+ }
+ )
+ _loading.value = false
+ }
+ }
+
+ internal fun favoritePerson(people: People) {
+ viewModelScope.launch {
+ people.id?.let { favoritePerson.execute(people.favorite, it) }
+ }
+ }
+}
\ 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 00000000..2b068d11
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml
new file mode 100644
index 00000000..cf05f78f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_favorite.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_favorite_border.xml b/app/src/main/res/drawable/ic_favorite_border.xml
new file mode 100644
index 00000000..8438024a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_favorite_border.xml
@@ -0,0 +1,10 @@
+
+
+
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 00000000..07d5da9c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..45ea4512
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_detail_people.xml b/app/src/main/res/layout/fragment_detail_people.xml
new file mode 100644
index 00000000..aa9b2571
--- /dev/null
+++ b/app/src/main/res/layout/fragment_detail_people.xml
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_list_people.xml b/app/src/main/res/layout/fragment_list_people.xml
new file mode 100644
index 00000000..958f572c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_list_people.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_people.xml b/app/src/main/res/layout/item_people.xml
new file mode 100644
index 00000000..515a6509
--- /dev/null
+++ b/app/src/main/res/layout/item_people.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/loading.xml b/app/src/main/res/layout/loading.xml
new file mode 100644
index 00000000..e148d18a
--- /dev/null
+++ b/app/src/main/res/layout/loading.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_filter.xml b/app/src/main/res/menu/menu_filter.xml
new file mode 100644
index 00000000..bd2b22e6
--- /dev/null
+++ b/app/src/main/res/menu/menu_filter.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..a571e600
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..61da551c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..c41dd285
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..db5080a7
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..6dba46da
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..da31a871
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..15ac6817
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..b216f2d3
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..f25a4197
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..e96783cc
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 00000000..af3fa5b6
--- /dev/null
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..55f1aad1
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..028a7016
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+ Star Wars
+ Altura
+ Massa
+ GĂȘnero
+ Cor do cabelo
+ Cor da pele
+ Cor dos olhos
+ Ano nascimento
+ Planeta
+ Espécie
+ n/a
+ Carregando
+ Todos
+ Favoritos
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..47b33206
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/br/com/example/starwars/ExampleUnitTest.kt b/app/src/test/java/br/com/example/starwars/ExampleUnitTest.kt
new file mode 100644
index 00000000..43c8872d
--- /dev/null
+++ b/app/src/test/java/br/com/example/starwars/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package br.com.example.starwars
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..70f42f1f
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,72 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext {
+ // Api EndPoint
+ apiEndpoint = "https://swapi.dev/api"
+
+ // Kotlin
+ kotlin_version = '1.6.10'
+ kotlinCoreVersion = '1.7.0'
+
+ // Android
+ gradleVersion = '7.0.4'
+ appcompatVersion = '1.4.0'
+ constraintlayoutVersion = '2.0.4'
+ materialVersion = '1.2.1'
+
+ // LifeCycle
+ lifeCycleVersion = '2.2.0'
+ viewModelVersion = '2.2.0'
+ liveDataVersion= '2.2.0'
+
+ // Retrofit
+ retrofitVersion = '2.9.0'
+
+ // Room
+ roomVersion = '2.4.0'
+
+ // Paging
+ pagingVersion = '3.1.0'
+
+ // Navigation
+ navVersion = '2.3.5'
+ navSafeArgs = '2.3.5'
+
+ // Hilt
+ hiltVersion = '2.39.1'
+
+ //Tests
+ junitVersion = '4.13.1'
+ junitExtVersion = '1.1.2'
+ espressoVersion = '3.3.0'
+ }
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:$gradleVersion"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "com.google.dagger:hilt-android-gradle-plugin:$hiltVersion"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navSafeArgs"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ configurations.all {
+ resolutionStrategy {
+ force("org.xerial:sqlite-jdbc:3.34.0")
+ }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..98bed167
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f6b961fd
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ae8551d4
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Jan 04 00:11:11 AMT 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..cccdd3d5
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..898ae885
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "Star Wars"
\ No newline at end of file