diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..61342e3b3
Binary files /dev/null and b/.DS_Store differ
diff --git a/TesteAndroid/.gitignore b/TesteAndroid/.gitignore
new file mode 100644
index 000000000..603b14077
--- /dev/null
+++ b/TesteAndroid/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/TesteAndroid/.idea/$CACHE_FILE$ b/TesteAndroid/.idea/$CACHE_FILE$
new file mode 100644
index 000000000..f9784d3c7
--- /dev/null
+++ b/TesteAndroid/.idea/$CACHE_FILE$
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+ Android
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/.name b/TesteAndroid/.idea/.name
new file mode 100644
index 000000000..e8020f579
--- /dev/null
+++ b/TesteAndroid/.idea/.name
@@ -0,0 +1 @@
+Teste Android
\ No newline at end of file
diff --git a/TesteAndroid/.idea/codeStyles/Project.xml b/TesteAndroid/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000..88ea3aa1e
--- /dev/null
+++ b/TesteAndroid/.idea/codeStyles/Project.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/codeStyles/codeStyleConfig.xml b/TesteAndroid/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000..79ee123c2
--- /dev/null
+++ b/TesteAndroid/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/dictionaries b/TesteAndroid/.idea/dictionaries
new file mode 100644
index 000000000..2ada05a92
--- /dev/null
+++ b/TesteAndroid/.idea/dictionaries
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/gradle.xml b/TesteAndroid/.idea/gradle.xml
new file mode 100644
index 000000000..ac6b0aec6
--- /dev/null
+++ b/TesteAndroid/.idea/gradle.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/jarRepositories.xml b/TesteAndroid/.idea/jarRepositories.xml
new file mode 100644
index 000000000..a5f05cd8c
--- /dev/null
+++ b/TesteAndroid/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/misc.xml b/TesteAndroid/.idea/misc.xml
new file mode 100644
index 000000000..37a750962
--- /dev/null
+++ b/TesteAndroid/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/runConfigurations.xml b/TesteAndroid/.idea/runConfigurations.xml
new file mode 100644
index 000000000..7f68460d8
--- /dev/null
+++ b/TesteAndroid/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/.idea/vcs.xml b/TesteAndroid/.idea/vcs.xml
new file mode 100644
index 000000000..6c0b86358
--- /dev/null
+++ b/TesteAndroid/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/READ.md b/TesteAndroid/READ.md
new file mode 100644
index 000000000..34b64a67c
--- /dev/null
+++ b/TesteAndroid/READ.md
@@ -0,0 +1,33 @@
+# Overview sobre o projeto
+
+Seguem algumas considerações sobre a arquitetura utilizada no projeto:
+
+Utilizei MVVMClean como arquitetura, separando as camadas em Fragment, ViewModel, Handler e Presenter.
+
+Segue um resumo da responsabilidade de cada camada:
+- No fragment ficam basicamente os observers dos objetos LiveData adicionados nas ViewModels e nos ViewPresenters, atualizando os dados da
+interface diretamente no xml, utilizando databinding.
+- Na ViewModel ficam os objetos LiveData, os eventos de click de botão e as funções que controlam as ações do usuário, de acordo com ação de cada view.
+Também ficam na ViewModel as requisições ao serviço, por meio da classe Handler.
+- Por sua vez, a classe Handler é responsável por trabalhar com os dados requisitados pelo usuário. Sejam eles por meio de input de dados ou ações/requisições.
+ - É nela onde são feitas as chamadas à classe Service, que por sua vez busca/envia os dados usando a classe Repository.
+ - Também é nela onde enviam-se os dados trabalhados para objetos na classe Presenter, que por sua vez guarda dados de input,
+ estados das Views (selected, enabled), label texts, etc. e gerencia estados de componentes através de objetos LiveData.
+
+Utilizei injeção de dependência com a biblioteca Koin, que também foi utilizada nas rotinas de teste, utilizando a classe KoinTest.
+Para as reqisições à API utilizei Retrofit.
+
+### # Observações gerais
+
+Na tela de login, há a validação de usuário e senha, onde para habilitar o botão de login, é necessário digitar os dados de acordo com a validação
+sugerida (nome de usuário utilizando email ou cpf, e senha com ao menos 1 número, 1 caractere especial e uma letra maiúscula).
+Fiz a validação desta maneira e não após o toque no botão de Sign in pois acredito que neste cenário entende-se que o usuário á está cadastrado e sabe
+das regras de cadastro de nome de usuário e senha.
+
+*Para executar o projeto basta abrir no Android Studio, aguardar o gradle sincronizar as dependências e executar o mesmo.
+
+# OBRIGADO PELA OPORTUNIDADE!
+
+#MARLON
+
+#Estou me candidatando através da empresa HProjekt. Recrutador/Contato: Luiz Pontes.
diff --git a/TesteAndroid/app/.gitignore b/TesteAndroid/app/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/TesteAndroid/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/TesteAndroid/app/build.gradle b/TesteAndroid/app/build.gradle
new file mode 100644
index 000000000..d7f909b17
--- /dev/null
+++ b/TesteAndroid/app/build.gradle
@@ -0,0 +1,62 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: "androidx.navigation.safeargs.kotlin"
+
+android {
+ compileSdkVersion 29
+
+ buildFeatures {
+ dataBinding = true
+ }
+
+ defaultConfig {
+ applicationId "br.com.mdr.testeandroid"
+ minSdkVersion 19
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation 'androidx.core:core-ktx:1.3.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
+
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
+
+ // Koin
+ implementation "org.koin:koin-androidx-scope:2.1.6"
+ implementation "org.koin:koin-androidx-viewmodel:2.1.1"
+ testImplementation "org.koin:koin-test:2.1.6"
+
+ // Retrofit
+ implementation 'com.squareup.retrofit2:retrofit:2.6.2'
+ implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
+
+ // OkHttp
+ implementation "com.squareup.okhttp3:logging-interceptor:3.12.1"
+
+ testImplementation 'android.arch.core:core-testing:1.1.1'
+ testImplementation 'junit:junit:4.12'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.7'
+
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/proguard-rules.pro b/TesteAndroid/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/TesteAndroid/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/TesteAndroid/app/src/androidTest/java/br/com/mdr/testeandroid/ExampleInstrumentedTest.kt b/TesteAndroid/app/src/androidTest/java/br/com/mdr/testeandroid/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..6b6879391
--- /dev/null
+++ b/TesteAndroid/app/src/androidTest/java/br/com/mdr/testeandroid/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package br.com.mdr.testeandroid
+
+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.mdr.testeandroid", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/AndroidManifest.xml b/TesteAndroid/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..59ae6b4f9
--- /dev/null
+++ b/TesteAndroid/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/App.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/App.kt
new file mode 100644
index 000000000..b09e83b43
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/App.kt
@@ -0,0 +1,38 @@
+package br.com.mdr.testeandroid
+
+import android.app.Application
+import br.com.mdr.testeandroid.di.*
+import org.koin.android.ext.koin.androidContext
+
+import org.koin.core.context.startKoin
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+class App: Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ setupKoin()
+ }
+
+ private fun setupKoin() {
+ startKoin {
+ androidContext(this@App)
+ modules(
+ listOf(
+ apiModule,
+ modelModule,
+ networkModule,
+ repositoryModule,
+ serviceModule,
+ viewModelModule,
+ preferencesModule,
+ adapterModule,
+ presenterModule
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/adapter/AdapterItemsContract.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/adapter/AdapterItemsContract.kt
new file mode 100644
index 000000000..b5b74b4ff
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/adapter/AdapterItemsContract.kt
@@ -0,0 +1,8 @@
+package br.com.mdr.testeandroid.adapter
+
+
+interface AdapterItemsContract {
+
+ fun replaceItens(list: List<*>)
+
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/adapter/StatementAdapter.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/adapter/StatementAdapter.kt
new file mode 100644
index 000000000..ae6c72bf0
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/adapter/StatementAdapter.kt
@@ -0,0 +1,41 @@
+package br.com.mdr.testeandroid.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import br.com.mdr.testeandroid.databinding.DashboardStatementItemBinding
+import br.com.mdr.testeandroid.model.business.Statement
+
+/**
+ * @author Marlon D. Rocha
+ * @since 07/07/20
+ */
+class StatementAdapter: RecyclerView.Adapter(), AdapterItemsContract {
+
+ private var itens: MutableList? = null
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatementViewHolder {
+ val inflater = LayoutInflater.from(parent.context)
+ val binding = DashboardStatementItemBinding.inflate(inflater, parent, false)
+ return StatementViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: StatementViewHolder, position: Int) {
+ val statement = itens!![position]
+ holder.bind(statement)
+ }
+
+ override fun getItemCount() = if (itens != null) itens!!.size else 0
+
+ @Suppress("UNCHECKED_CAST")
+ override fun replaceItens(list: List<*>) {
+ this.itens = list as MutableList
+ notifyDataSetChanged()
+ }
+
+ class StatementViewHolder(private val binding: DashboardStatementItemBinding): RecyclerView.ViewHolder(binding.root) {
+ fun bind(statement: Statement) {
+ binding.statement = statement
+ }
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/api/DashboardApi.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/api/DashboardApi.kt
new file mode 100644
index 000000000..bf11f1b3b
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/api/DashboardApi.kt
@@ -0,0 +1,20 @@
+package br.com.mdr.testeandroid.api
+
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Path
+
+/**
+ * @author Marlon D. Rocha
+ * @since 07/07/20
+ */
+interface DashboardApi {
+ companion object {
+ private const val USER_ID_PARAM = "id"
+ private const val STATEMENTS_PATH = "statements/{$USER_ID_PARAM}"
+ }
+
+ @GET(STATEMENTS_PATH)
+ suspend fun getStatements(@Path(USER_ID_PARAM) userId: String): Response
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/api/SignInApi.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/api/SignInApi.kt
new file mode 100644
index 000000000..2e3bc8831
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/api/SignInApi.kt
@@ -0,0 +1,20 @@
+package br.com.mdr.testeandroid.api
+
+import br.com.mdr.testeandroid.model.api.SignInApiModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.POST
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+interface SignInApi {
+ companion object {
+ private const val SIGN_IN_PATH = "login"
+ }
+
+ @POST(SIGN_IN_PATH)
+ suspend fun signInUser(@Body user: SignInApiModel): Response
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ApiModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ApiModule.kt
new file mode 100644
index 000000000..9f02c9b8f
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ApiModule.kt
@@ -0,0 +1,16 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.api.DashboardApi
+import br.com.mdr.testeandroid.api.SignInApi
+import org.koin.dsl.module
+import retrofit2.Retrofit
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+
+val apiModule = module {
+ single { get().create(SignInApi::class.java) }
+ single { get().create(DashboardApi::class.java) }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ModelModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ModelModule.kt
new file mode 100644
index 000000000..ba5018eb4
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ModelModule.kt
@@ -0,0 +1,21 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.model.api.UserApiModel
+import br.com.mdr.testeandroid.model.business.Statement
+import br.com.mdr.testeandroid.model.business.User
+import org.koin.dsl.module
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+
+val modelModule = module {
+
+ // Business
+ factory { User() }
+ single { Statement() }
+
+ // Api
+ factory { UserApiModel() }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/NetworkModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/NetworkModule.kt
new file mode 100644
index 000000000..f3f158abd
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/NetworkModule.kt
@@ -0,0 +1,70 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.util.Constants.Companion.BASE_URL
+import com.google.gson.GsonBuilder
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import org.koin.dsl.module
+import retrofit2.Converter
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/2020
+ */
+
+val networkModule = module {
+
+ // Retrofit
+ single {
+ Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .addConverterFactory(get())
+ .client(get())
+ .build()
+ }
+
+ // OkHttp Client
+ single {
+ OkHttpClient.Builder()
+ .addInterceptor(get())
+ .addInterceptor(get())
+ .build()
+ }
+
+ // Http Logging Interceptor
+ single {
+ HttpLoggingInterceptor(
+ HttpLoggingInterceptor.Logger {}
+ ).apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+ }
+
+ // Interceptor
+ single {
+ Interceptor { chain ->
+ chain.request().run {
+ newBuilder()
+ .addHeader("Accept", "application/json")
+ .addHeader("Content-type", "application/json")
+ .method(this.method(), this.body())
+ .build()
+ .let(chain::proceed)
+ }
+ }
+ }
+
+ // Gson
+ single {
+ GsonBuilder().create()
+ }
+
+ // GsonConverterFactory
+ single {
+ GsonConverterFactory.create(get()) as Converter.Factory
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/PreferencesModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/PreferencesModule.kt
new file mode 100644
index 000000000..3534b6701
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/PreferencesModule.kt
@@ -0,0 +1,18 @@
+package br.com.mdr.testeandroid.di
+
+import android.app.Application
+import android.content.SharedPreferences
+import br.com.mdr.testeandroid.util.Constants.Companion.PREFERENCES_FILE_KEY
+import org.koin.android.ext.koin.androidApplication
+import org.koin.dsl.module
+
+val preferencesModule = module {
+ single { getSharedPrefs(androidApplication()) }
+ single {
+ getSharedPrefs(androidApplication()).edit()
+ }
+}
+
+private fun getSharedPrefs(app: Application): SharedPreferences {
+ return app.getSharedPreferences(PREFERENCES_FILE_KEY, android.content.Context.MODE_PRIVATE)
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/PresenterModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/PresenterModule.kt
new file mode 100644
index 000000000..22b7836dc
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/PresenterModule.kt
@@ -0,0 +1,17 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.flow.dashboard.DashboardHandler
+import br.com.mdr.testeandroid.flow.signin.SignInHandler
+import br.com.mdr.testeandroid.flow.signin.SignInViewPresenter
+import org.koin.dsl.module
+
+val presenterModule = module {
+
+ //SignIn
+ single { SignInHandler(get(), get()) }
+ single { SignInViewPresenter() }
+
+ //Dashboard
+ single { DashboardHandler(get()) }
+
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/RepositoryModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/RepositoryModule.kt
new file mode 100644
index 000000000..a64c55ae2
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/RepositoryModule.kt
@@ -0,0 +1,15 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.repository.DashboardRepository
+import br.com.mdr.testeandroid.repository.SignInRepository
+import org.koin.dsl.module
+
+/**
+ * @author Marlon D. Rocha
+ * @since 05/07/2020
+ */
+
+val repositoryModule = module {
+ single { SignInRepository(get(), get()) }
+ single { DashboardRepository(get()) }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ServiceModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ServiceModule.kt
new file mode 100644
index 000000000..6d2322254
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ServiceModule.kt
@@ -0,0 +1,15 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.service.DashboardService
+import br.com.mdr.testeandroid.service.SignInService
+import org.koin.dsl.module
+
+/**
+ * @author Marlon D. Rocha
+ * @since 05/07/2020
+ */
+
+val serviceModule = module {
+ single { SignInService(get()) }
+ single { DashboardService(get()) }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ViewModelModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ViewModelModule.kt
new file mode 100644
index 000000000..1d2dd8105
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/ViewModelModule.kt
@@ -0,0 +1,16 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.flow.dashboard.DashboardViewModel
+import br.com.mdr.testeandroid.flow.signin.SignInViewModel
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
+
+/**
+ * @author Marlon D. Rocha
+ * @since 05/07/2020
+ */
+
+val viewModelModule = module {
+ viewModel { SignInViewModel(signInHandler = get()) }
+ viewModel { DashboardViewModel(get()) }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/adapterModule.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/adapterModule.kt
new file mode 100644
index 000000000..1478e36ca
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/di/adapterModule.kt
@@ -0,0 +1,13 @@
+package br.com.mdr.testeandroid.di
+
+import br.com.mdr.testeandroid.adapter.StatementAdapter
+import org.koin.dsl.module
+
+/**
+ * @author Marlon D. Rocha
+ * @since 07/07/20
+ */
+
+val adapterModule = module {
+ single { StatementAdapter() }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/ActivityExtensions.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/ActivityExtensions.kt
new file mode 100644
index 000000000..9897f40c1
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/ActivityExtensions.kt
@@ -0,0 +1,21 @@
+package br.com.mdr.testeandroid.extensions
+
+import android.app.Activity
+import android.os.Build
+import android.view.View
+import androidx.core.content.ContextCompat
+
+/**
+ * @author Marlon D. Rocha
+ * @since 07/07/20
+ */
+
+fun Activity.setLightStatusBar(isLight: Boolean) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ window?.decorView?.systemUiVisibility = if (isLight) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0
+}
+
+fun Activity.setStatusBarColor(color: Int) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ window?.statusBarColor = ContextCompat.getColor(this, color)
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/ButtonExtensions.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/ButtonExtensions.kt
new file mode 100644
index 000000000..594cef4e5
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/ButtonExtensions.kt
@@ -0,0 +1,17 @@
+package br.com.mdr.testeandroid.extensions
+
+import android.content.Context
+import android.content.res.ColorStateList
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import com.google.android.material.button.MaterialButton
+
+/**
+ * @author Marlon D. Rocha
+ * @since 05/07/2020
+ */
+
+fun MaterialButton.changeBackgroundColor(@ColorRes color: Int, context: Context) {
+ val buttonColor = ContextCompat.getColor(context, color)
+ backgroundTintList = ColorStateList.valueOf(buttonColor)
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/DoubleExtensions.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/DoubleExtensions.kt
new file mode 100644
index 000000000..4e87bb1e0
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/DoubleExtensions.kt
@@ -0,0 +1,13 @@
+package br.com.mdr.testeandroid.extensions
+
+import java.text.NumberFormat
+import java.util.*
+
+/**
+ * @author Marlon D. Rocha
+ * @since 07/07/20
+ */
+
+fun Double.formattedCurrency(): String {
+ return NumberFormat.getCurrencyInstance(Locale("pt", "BR")).format(this)
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/FragmentExtensions.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/FragmentExtensions.kt
new file mode 100644
index 000000000..c3658e2a4
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/FragmentExtensions.kt
@@ -0,0 +1,32 @@
+package br.com.mdr.testeandroid.extensions
+
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import br.com.mdr.testeandroid.R
+import com.google.android.material.snackbar.Snackbar
+
+/**
+ * @author Marlon D. Rocha
+ * @since 08/07/20
+ */
+
+fun Fragment.showErrorSnack(snackMessage: String) {
+ val snackBar = Snackbar.make(this.requireView(), snackMessage, Snackbar.LENGTH_LONG)
+ val textId = com.google.android.material.R.id.snackbar_text
+ val snackView = snackBar.view
+ val txtSnack = snackView.findViewById(textId)
+ txtSnack.maxLines = 5
+ val params = snackView.layoutParams as FrameLayout.LayoutParams
+ val sideMargin = 16
+ params.setMargins(params.leftMargin + sideMargin,
+ params.topMargin,
+ params.rightMargin + sideMargin,
+ params.bottomMargin + sideMargin)
+ snackView.layoutParams = params
+ snackView.setBackgroundResource(R.drawable.error_snack_corner)
+
+ txtSnack.setTextColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary))
+ snackBar.show()
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/LiveDataExtensions.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/LiveDataExtensions.kt
new file mode 100644
index 000000000..7d5d55024
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/LiveDataExtensions.kt
@@ -0,0 +1,39 @@
+package br.com.mdr.testeandroid.extensions
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+/**
+ * @author Marlon D. Rocha
+ * @since 09/07/20
+ */
+
+fun LiveData.getOrAwaitValue(
+ time: Long = 2,
+ timeUnit: TimeUnit = TimeUnit.SECONDS,
+ afterObserve: () -> Unit = {}
+): T {
+ var data: T? = null
+ val latch = CountDownLatch(1)
+ val observer = object : Observer {
+ override fun onChanged(o: T?) {
+ data = o
+ latch.countDown()
+ this@getOrAwaitValue.removeObserver(this)
+ }
+ }
+ this.observeForever(observer)
+
+ afterObserve.invoke()
+
+ if (!latch.await(time, timeUnit)) {
+ this.removeObserver(observer)
+ throw TimeoutException("LiveData value was never set.")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return data as T
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/StringExtensions.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/StringExtensions.kt
new file mode 100644
index 000000000..b9fdc93de
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/extensions/StringExtensions.kt
@@ -0,0 +1,75 @@
+package br.com.mdr.testeandroid.extensions
+
+import android.text.TextUtils
+import br.com.mdr.testeandroid.util.Constants.Companion.REGEX_SPECIAL_CHARS
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+fun String.hasDigit(): Boolean {
+ var hasDigit = false
+ for (letter in this) {
+ if (!hasDigit)
+ hasDigit = letter.isDigit()
+ }
+ return hasDigit
+}
+
+fun String.hasUppercasedLetter(): Boolean {
+
+ for (letter in this) {
+ if (letter.isUpperCase())
+ return true
+ }
+ return false
+}
+
+fun String.isEmail(): Boolean {
+ return !TextUtils.isEmpty(this) && android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
+}
+
+fun String.hasSpecialCharacters(): Boolean {
+ val pattern: Pattern = Pattern.compile(REGEX_SPECIAL_CHARS)
+ val matcher: Matcher = pattern.matcher(this)
+
+ return !matcher.matches()
+}
+
+fun String.isCPF(): Boolean {
+ if (TextUtils.isEmpty(this)) return false
+
+ val numbers = arrayListOf()
+
+ this.filter { it.isDigit() }.forEach {
+ numbers.add(it.toString().toInt())
+ }
+
+ if (numbers.size != 11) return false
+
+ //repeticao
+ (0..9).forEach { n ->
+ val digits = arrayListOf()
+ (0..10).forEach { digits.add(n) }
+ if (numbers == digits) return false
+ }
+
+ //digito 1
+ val dv1 = ((0..8).sumBy { (it + 1) * numbers[it] }).rem(11).let {
+ if (it >= 10) 0 else it
+ }
+
+ val dv2 = ((0..8).sumBy { it * numbers[it] }.let { (it + (dv1 * 9)).rem(11) }).let {
+ if (it >= 10) 0 else it
+ }
+
+ return numbers[9] == dv1 && numbers[10] == dv2
+}
+
+fun String.formatDateBr(): String {
+ val deFaultformatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ val convertedDate = deFaultformatter.parse(this)
+
+ val brFormatter = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+ return brFormatter.format(convertedDate)
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardFragment.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardFragment.kt
new file mode 100644
index 000000000..7c9ff7cea
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardFragment.kt
@@ -0,0 +1,83 @@
+package br.com.mdr.testeandroid.flow.dashboard
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Observer
+import br.com.mdr.testeandroid.R
+import br.com.mdr.testeandroid.adapter.StatementAdapter
+import br.com.mdr.testeandroid.databinding.DashboardFragmentBinding
+import br.com.mdr.testeandroid.extensions.setLightStatusBar
+import br.com.mdr.testeandroid.extensions.setStatusBarColor
+import br.com.mdr.testeandroid.extensions.showErrorSnack
+import br.com.mdr.testeandroid.util.Constants.Companion.USER_KEY
+import org.koin.android.ext.android.inject
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class DashboardFragment : Fragment() {
+ private val viewModel: DashboardViewModel by viewModel()
+ private val adapter: StatementAdapter by inject()
+ private var binding: DashboardFragmentBinding? = null
+ private val preferencesEditor: SharedPreferences.Editor by inject()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = DashboardFragmentBinding.inflate(inflater)
+ binding?.let {
+ it.recyclerStatements.adapter = adapter
+ setupListeners(it)
+ }
+ return binding?.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ activity?.setStatusBarColor(R.color.colorAccent)
+ activity?.setLightStatusBar(false)
+
+ setupObservables()
+ if (arguments != null) {
+ arguments?.let {
+ val user = DashboardFragmentArgs.fromBundle(it).usuario
+ binding?.user = user
+ viewModel.fetchUserStatements(user)
+ }
+ }
+ }
+
+ private fun setupObservables() {
+ viewModel.statementsLive.observe(viewLifecycleOwner, Observer { statements ->
+ statements?.let {
+ adapter.replaceItens(it)
+ }
+ })
+
+ viewModel.errorLive.observe( viewLifecycleOwner, Observer { error ->
+ if (error?.code != 0)
+ error?.message?.let { showErrorSnack(it) }
+ })
+
+ viewModel.isLoading.observe(viewLifecycleOwner, Observer {
+ binding?.apply { showLoading = it }
+ })
+ }
+
+ private fun setupListeners(binding: DashboardFragmentBinding) {
+ binding.apply {
+ btnSignOut.setOnClickListener{
+ signOutUser()
+ viewModel.clickListener()
+ }
+ }
+ }
+
+ private fun signOutUser() {
+ preferencesEditor.remove(USER_KEY)
+ preferencesEditor.apply()
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardHandler.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardHandler.kt
new file mode 100644
index 000000000..38f88239c
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardHandler.kt
@@ -0,0 +1,19 @@
+package br.com.mdr.testeandroid.flow.dashboard
+
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+import br.com.mdr.testeandroid.service.DashboardService
+
+class DashboardHandler(
+ override val service: DashboardService
+) : IDashboardHandler {
+
+ override suspend fun fetchStatements(userId: Int): DashboardApiModel {
+ var apiResult = DashboardApiModel()
+
+ service.getStatements(userId)?.let {
+ apiResult = it
+ }
+
+ return apiResult
+ }
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardViewModel.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardViewModel.kt
new file mode 100644
index 000000000..e7bef1452
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/DashboardViewModel.kt
@@ -0,0 +1,47 @@
+package br.com.mdr.testeandroid.flow.dashboard
+
+import android.view.View
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.viewModelScope
+import androidx.navigation.findNavController
+import br.com.mdr.testeandroid.R
+import br.com.mdr.testeandroid.flow.main.BaseViewModel
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+import br.com.mdr.testeandroid.model.business.Error
+import br.com.mdr.testeandroid.model.business.Statement
+import br.com.mdr.testeandroid.model.business.User
+import kotlinx.coroutines.launch
+
+class DashboardViewModel(
+ private val dashboardHandler: DashboardHandler
+) : BaseViewModel() {
+ private lateinit var _user: User
+ val statementsLive: MutableLiveData?> = MutableLiveData()
+ var errorLive: MutableLiveData = MutableLiveData()
+
+ fun clickListener() = View.OnClickListener {
+ when (it.id) {
+ R.id.btnSignOut -> { signOutUser(it)}
+ }
+ }
+
+ private fun signOutUser(view: View) {
+ val direction = DashboardFragmentDirections.actionDashboardFragmentToSignInFragment()
+ view.findNavController().navigate(direction)
+ }
+
+ fun fetchUserStatements(user: User) {
+ isLoading.value = true
+ _user = user
+ viewModelScope.launch {
+ val apiResult = dashboardHandler.fetchStatements(_user.userId!!)
+ fetchApiResult(apiResult)
+ }
+ }
+
+ private fun fetchApiResult(result: DashboardApiModel) {
+ result.statementList?.let { statementsLive.value = it }
+ result.error?.let { errorLive.value = it }
+ isLoading.postValue(false)
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/IDashboardHandler.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/IDashboardHandler.kt
new file mode 100644
index 000000000..f7b3a096d
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/dashboard/IDashboardHandler.kt
@@ -0,0 +1,9 @@
+package br.com.mdr.testeandroid.flow.dashboard
+
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+import br.com.mdr.testeandroid.service.IDashboardService
+
+interface IDashboardHandler {
+ val service: IDashboardService
+ suspend fun fetchStatements(userId: Int): DashboardApiModel
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/main/BaseViewModel.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/main/BaseViewModel.kt
new file mode 100644
index 000000000..7c360d8e4
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/main/BaseViewModel.kt
@@ -0,0 +1,12 @@
+package br.com.mdr.testeandroid.flow.main
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+/**
+ * @author Marlon D. Rocha
+ * @since 10/07/20
+ */
+open class BaseViewModel: ViewModel() {
+ var isLoading: MutableLiveData = MutableLiveData(false)
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/main/MainActivity.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/main/MainActivity.kt
new file mode 100644
index 000000000..0e03e967c
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/main/MainActivity.kt
@@ -0,0 +1,26 @@
+package br.com.mdr.testeandroid.flow.main
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import br.com.mdr.testeandroid.databinding.ActivityMainBinding
+
+class MainActivity : AppCompatActivity() {
+ //private val activityPresenter: LoadingPresenter by inject()
+ private lateinit var mainBinding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mainBinding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(mainBinding.root)
+ //setupObservables()
+ }
+
+// private fun setupObservables() {
+// activityPresenter.isLoading.observe(this, Observer {
+// GlobalScope.launch {
+// withContext(Dispatchers.Main) { mainBinding.showLoading = it }
+// }
+// })
+// }
+
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/ISignInHandler.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/ISignInHandler.kt
new file mode 100644
index 000000000..40d19116d
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/ISignInHandler.kt
@@ -0,0 +1,10 @@
+package br.com.mdr.testeandroid.flow.signin
+
+import br.com.mdr.testeandroid.service.ISignInService
+
+interface ISignInHandler {
+ val signInPresenter: ISignInViewPresenter
+ val service: ISignInService
+ fun onUserNameChanged(userName: CharSequence)
+ fun onPasswordChanged(password: CharSequence)
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/ISignInViewPresenter.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/ISignInViewPresenter.kt
new file mode 100644
index 000000000..721455231
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/ISignInViewPresenter.kt
@@ -0,0 +1,12 @@
+package br.com.mdr.testeandroid.flow.signin
+
+import androidx.lifecycle.MutableLiveData
+
+interface ISignInViewPresenter {
+ var buttonEnabledLive: MutableLiveData
+ var userName: String
+ var password: String
+ var maskedUserName: MutableLiveData
+
+ fun handleButtonState()
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInFragment.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInFragment.kt
new file mode 100644
index 000000000..336bfe039
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInFragment.kt
@@ -0,0 +1,116 @@
+package br.com.mdr.testeandroid.flow.signin
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Observer
+import br.com.mdr.testeandroid.R
+import br.com.mdr.testeandroid.databinding.SignInFragmentBinding
+import br.com.mdr.testeandroid.extensions.*
+import br.com.mdr.testeandroid.model.business.User
+import br.com.mdr.testeandroid.util.Constants.Companion.USER_KEY
+import com.google.gson.Gson
+import kotlinx.android.synthetic.main.sign_in_fragment.*
+import org.koin.android.ext.android.inject
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class SignInFragment : Fragment() {
+ private var binding: SignInFragmentBinding? = null
+ //Caso a viewmodel venha a ser utilizada por mais de um Fragment, pode ser utilizado o escopo sharedViewModel()
+ private val viewModel: SignInViewModel by viewModel()
+ private val preferences: SharedPreferences by inject()
+ private val preferencesEditor: SharedPreferences.Editor by inject()
+ private val gson: Gson by inject()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = SignInFragmentBinding.inflate(inflater)
+ binding?.let {
+ it.handler = viewModel.signInHandler
+ setupListeners(it)
+ }
+ return binding?.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupObservables()
+ checkLoggedUser()
+ activity?.setStatusBarColor(R.color.white)
+ activity?.setLightStatusBar(true)
+ }
+
+ override fun onDestroyView() {
+ binding = null
+ super.onDestroyView()
+ }
+
+ private fun setupObservables() {
+ viewModel.errorLive.observe( viewLifecycleOwner, Observer { error ->
+ if (error?.code != 0)
+ error?.message?.let { showErrorSnack(it) }
+ })
+
+ viewModel.signInHandler.signInPresenter.buttonEnabledLive.observe(viewLifecycleOwner, Observer { isEnabled ->
+ binding.apply {
+ btnSignIn?.isEnabled = isEnabled
+ val buttonColor =
+ if (isEnabled)
+ R.color.colorAccent
+ else
+ R.color.disabled_background
+ btnSignIn?.changeBackgroundColor(buttonColor, requireContext())
+ }
+ })
+
+ viewModel.signInHandler.signInPresenter.maskedUserName.observe(viewLifecycleOwner, Observer { maskedUserName ->
+ binding?.let { edtUserName.setText(maskedUserName) }
+ })
+
+ viewModel.showUserInfo.observe(viewLifecycleOwner, Observer {
+ binding?.showUserAccount = it
+ })
+
+ viewModel.userLive.observe(viewLifecycleOwner, Observer { user ->
+ binding?.let {
+ if (viewModel.isUserLogged) {
+ it.user = user
+ it.showUserAccount = user?.userId != null
+ val welComeString = "${resources.getString(R.string.hello_user)} ${user?.name}"
+ it.welcomeLabel = welComeString
+ } else {
+ saveUser(user)
+ it.btnSignInUser.callOnClick()
+ }
+ }
+ })
+
+ viewModel.isLoading.observe(viewLifecycleOwner, Observer {
+ binding?.apply { showLoading = it }
+ })
+ }
+
+ private fun saveUser(user: User) {
+ val strUser = gson.toJson(user)
+ preferencesEditor.putString(USER_KEY, strUser)
+ preferencesEditor.apply()
+ }
+
+ private fun checkLoggedUser() {
+ val strUser = preferences.getString(USER_KEY, "")
+ val user = gson.fromJson(strUser, User::class.java)
+ viewModel.setUser(user)
+ }
+
+ private fun setupListeners(binding: SignInFragmentBinding) {
+ binding.apply {
+ clickListener = viewModel.manageOnClick()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInHandler.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInHandler.kt
new file mode 100644
index 000000000..5872e8dc4
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInHandler.kt
@@ -0,0 +1,53 @@
+package br.com.mdr.testeandroid.flow.signin
+
+import br.com.mdr.testeandroid.extensions.isCPF
+import br.com.mdr.testeandroid.model.api.SignInApiModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+import br.com.mdr.testeandroid.service.SignInService
+import br.com.mdr.testeandroid.util.MaskUtil
+
+class SignInHandler(
+ override val signInPresenter: SignInViewPresenter,
+ override val service: SignInService
+) : ISignInHandler {
+ private var maskedCpf = false
+
+ override fun onUserNameChanged(userName: CharSequence) {
+ var userNameString = userName.toString()
+
+ if (userNameString.length == 14 && maskedCpf) {
+ userNameString = MaskUtil.removeMask(userName.toString())
+ }
+
+ if (userNameString.isCPF() && !maskedCpf) {
+ maskedCpf = true
+ signInPresenter.maskedUserName.value = MaskUtil.getCpfMask(userNameString)
+ } else {
+ maskedCpf = false
+ }
+
+ signInPresenter.userName = userNameString
+ handleButtonState()
+ }
+
+ override fun onPasswordChanged(password: CharSequence) {
+ signInPresenter.password = password.toString()
+ handleButtonState()
+ }
+
+ private fun handleButtonState() {
+ signInPresenter.handleButtonState()
+ }
+
+ suspend fun callSignInUser(): UserApiModel {
+ var apiResult = UserApiModel()
+
+ val signInApiModel = SignInApiModel(signInPresenter.userName, signInPresenter.password)
+ service.loginUser(signInApiModel)?.let {
+ signInPresenter.userName = ""
+ signInPresenter.password = ""
+ apiResult = it
+ }
+ return apiResult
+ }
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInViewModel.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInViewModel.kt
new file mode 100644
index 000000000..0fa7e2861
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInViewModel.kt
@@ -0,0 +1,60 @@
+package br.com.mdr.testeandroid.flow.signin
+
+import android.view.View
+import androidx.lifecycle.*
+import androidx.navigation.findNavController
+import br.com.mdr.testeandroid.R
+import br.com.mdr.testeandroid.flow.main.BaseViewModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+import br.com.mdr.testeandroid.model.business.Error
+import br.com.mdr.testeandroid.model.business.User
+import kotlinx.coroutines.launch
+
+class SignInViewModel(
+ val signInHandler: SignInHandler) : BaseViewModel() {
+ var showUserInfo: MutableLiveData = MutableLiveData(false)
+ var isUserLogged: Boolean = false
+ var userLive: MutableLiveData = MutableLiveData()
+ var errorLive: MutableLiveData = MutableLiveData()
+
+
+ fun manageOnClick() = View.OnClickListener {
+ when (it.id) {
+ R.id.btnSignIn -> {
+ callSignIn()
+ }
+ R.id.btnSignInAnother -> {
+ showUserInfo.value = false
+ }
+
+ R.id.btnSignInUser -> {
+ val userSaved = userLive.value
+ userSaved?.let { user ->
+ val direction = SignInFragmentDirections.actionSignInFragmentToDashboardFragment(usuario = user)
+ it.findNavController().navigate(direction)
+ }
+ }
+ }
+ }
+
+ fun setUser(user: User?) {
+ user?.let{
+ isUserLogged = true
+ userLive.value = it
+ }
+ }
+
+ fun callSignIn() {
+ isLoading.value = true
+ viewModelScope.launch {
+ val apiResult = signInHandler.callSignInUser()
+ fetchApiResult(apiResult)
+ }
+ }
+
+ private fun fetchApiResult(result: UserApiModel) {
+ result.userAccount?.let { userLive.value = it }
+ result.error?.let { errorLive.value = it }
+ isLoading.postValue(false)
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInViewPresenter.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInViewPresenter.kt
new file mode 100644
index 000000000..4dda6f495
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/flow/signin/SignInViewPresenter.kt
@@ -0,0 +1,17 @@
+package br.com.mdr.testeandroid.flow.signin
+
+import androidx.lifecycle.MutableLiveData
+import br.com.mdr.testeandroid.extensions.*
+
+class SignInViewPresenter(
+ override var buttonEnabledLive: MutableLiveData = MutableLiveData(false),
+ override var userName: String = "",
+ override var password: String = "",
+ override var maskedUserName: MutableLiveData = MutableLiveData("")
+) : ISignInViewPresenter {
+
+ override fun handleButtonState() {
+ buttonEnabledLive.value = (userName.isCPF() || userName.isEmail()) &&
+ (password.hasUppercasedLetter() && password.hasDigit() && password.hasSpecialCharacters())
+ }
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/DashboardApiModel.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/DashboardApiModel.kt
new file mode 100644
index 000000000..5d711b115
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/DashboardApiModel.kt
@@ -0,0 +1,14 @@
+package br.com.mdr.testeandroid.model.api
+
+import br.com.mdr.testeandroid.model.business.Error
+import br.com.mdr.testeandroid.model.business.Statement
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+
+data class DashboardApiModel(
+ var statementList: MutableList? = null,
+ var error: Error? = null
+)
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/SignInApiModel.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/SignInApiModel.kt
new file mode 100644
index 000000000..6399395ed
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/SignInApiModel.kt
@@ -0,0 +1,9 @@
+package br.com.mdr.testeandroid.model.api
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+class SignInApiModel (
+ val user: String,
+ val password: String)
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/UserApiModel.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/UserApiModel.kt
new file mode 100644
index 000000000..83209beef
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/api/UserApiModel.kt
@@ -0,0 +1,14 @@
+package br.com.mdr.testeandroid.model.api
+
+import br.com.mdr.testeandroid.model.business.Error
+import br.com.mdr.testeandroid.model.business.User
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+
+data class UserApiModel(
+ var userAccount: User? = null,
+ var error: Error? = null
+)
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/Error.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/Error.kt
new file mode 100644
index 000000000..1ee9bbcac
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/Error.kt
@@ -0,0 +1,6 @@
+package br.com.mdr.testeandroid.model.business
+
+data class Error(
+ val code: Int = 0,
+ val message: String = ""
+)
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/Statement.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/Statement.kt
new file mode 100644
index 000000000..90875283a
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/Statement.kt
@@ -0,0 +1,25 @@
+package br.com.mdr.testeandroid.model.business
+
+import br.com.mdr.testeandroid.extensions.formatDateBr
+import java.text.NumberFormat
+import java.util.*
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+data class Statement(
+ var title: String = "",
+ var desc: String = "",
+ var date: String = "",
+ var value: Double? = 0.0
+) {
+ fun getFormatedValue(): String {
+ return NumberFormat.getCurrencyInstance(Locale("pt", "BR"))
+ .format(value)
+ }
+
+ fun getFormatedDate(): String? {
+ return date.formatDateBr()
+ }
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/User.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/User.kt
new file mode 100644
index 000000000..e2399473a
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/model/business/User.kt
@@ -0,0 +1,26 @@
+package br.com.mdr.testeandroid.model.business
+
+import br.com.mdr.testeandroid.extensions.formattedCurrency
+import java.io.Serializable
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+data class User(
+ var userId: Int? = null,
+ var name: String? = null,
+ var bankAccount: String? = null,
+ var agency: String? = null,
+ var balance: Double? = null
+): Serializable {
+
+ fun getFullAccount(): String {
+ return "$agency / $bankAccount"
+ }
+
+ fun getFormattedBalance(): String {
+ balance?.let { return it.formattedCurrency() }
+ return ""
+ }
+}
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/BaseRepository.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/BaseRepository.kt
new file mode 100644
index 000000000..833080b3e
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/BaseRepository.kt
@@ -0,0 +1,10 @@
+package br.com.mdr.testeandroid.repository
+
+import retrofit2.Response
+
+abstract class BaseRepository {
+
+ protected fun handleResponse(response: Response): T? {
+ return response.body()
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/DashboardRepository.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/DashboardRepository.kt
new file mode 100644
index 000000000..e188d1954
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/DashboardRepository.kt
@@ -0,0 +1,17 @@
+package br.com.mdr.testeandroid.repository
+
+import br.com.mdr.testeandroid.api.DashboardApi
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+
+/**
+ * @author Marlon D. Rocha
+ * @since 07/07/20
+ */
+class DashboardRepository(
+ private val dashboardApi: DashboardApi
+) : BaseRepository(), IDashboardRepository {
+
+ override suspend fun getStatements(userId: Int): DashboardApiModel? {
+ return handleResponse(dashboardApi.getStatements(userId.toString()))
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/IDashboardRepository.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/IDashboardRepository.kt
new file mode 100644
index 000000000..16814d411
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/IDashboardRepository.kt
@@ -0,0 +1,7 @@
+package br.com.mdr.testeandroid.repository
+
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+
+interface IDashboardRepository {
+ suspend fun getStatements(userId: Int): DashboardApiModel?
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/ISignInRepository.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/ISignInRepository.kt
new file mode 100644
index 000000000..8c919a44e
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/ISignInRepository.kt
@@ -0,0 +1,8 @@
+package br.com.mdr.testeandroid.repository
+
+import br.com.mdr.testeandroid.model.api.SignInApiModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+
+interface ISignInRepository {
+ suspend fun signInUser(user: SignInApiModel): UserApiModel?
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/SignInRepository.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/SignInRepository.kt
new file mode 100644
index 000000000..f8048c3c8
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/repository/SignInRepository.kt
@@ -0,0 +1,19 @@
+package br.com.mdr.testeandroid.repository
+
+import br.com.mdr.testeandroid.api.SignInApi
+import br.com.mdr.testeandroid.model.api.SignInApiModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+import com.google.gson.Gson
+
+/**
+ * @author Marlon D. Rocha
+ * @since 04/07/20
+ */
+class SignInRepository(
+ private val signInApi: SignInApi
+) : BaseRepository(), ISignInRepository {
+
+ override suspend fun signInUser(user: SignInApiModel): UserApiModel? {
+ return handleResponse(signInApi.signInUser(user))
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/DashboardService.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/DashboardService.kt
new file mode 100644
index 000000000..15329ee49
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/DashboardService.kt
@@ -0,0 +1,14 @@
+package br.com.mdr.testeandroid.service
+
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+import br.com.mdr.testeandroid.repository.DashboardRepository
+
+
+class DashboardService(
+ private val dashboardRepository: DashboardRepository
+) : IDashboardService {
+
+ override suspend fun getStatements(userId: Int): DashboardApiModel? {
+ return dashboardRepository.getStatements(userId)
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/IDashboardService.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/IDashboardService.kt
new file mode 100644
index 000000000..9a2392b69
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/IDashboardService.kt
@@ -0,0 +1,14 @@
+package br.com.mdr.testeandroid.service
+
+import br.com.mdr.testeandroid.model.api.DashboardApiModel
+
+interface IDashboardService {
+
+ /**
+ * Get user statements
+ * @param userId The id of logged user
+ * @return List of user statements
+ * @throws when data is not valid for creation
+ */
+ suspend fun getStatements(userId: Int): DashboardApiModel?
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/ISignInService.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/ISignInService.kt
new file mode 100644
index 000000000..1e2ce31c2
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/ISignInService.kt
@@ -0,0 +1,15 @@
+package br.com.mdr.testeandroid.service
+
+import br.com.mdr.testeandroid.model.api.SignInApiModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+
+interface ISignInService {
+
+ /**
+ * Sign in user to app
+ * @param signInUser The user that will be created
+ * @return The authenticated user
+ * @throws when user data is not valid for creation
+ */
+ suspend fun loginUser(signInUser: SignInApiModel): UserApiModel?
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/SignInService.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/SignInService.kt
new file mode 100644
index 000000000..1d68d9c8d
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/service/SignInService.kt
@@ -0,0 +1,15 @@
+package br.com.mdr.testeandroid.service
+
+import br.com.mdr.testeandroid.model.api.SignInApiModel
+import br.com.mdr.testeandroid.model.api.UserApiModel
+import br.com.mdr.testeandroid.repository.SignInRepository
+
+
+class SignInService(
+ private val signInRepository: SignInRepository
+) : ISignInService {
+
+ override suspend fun loginUser(signInUser: SignInApiModel): UserApiModel? {
+ return signInRepository.signInUser(signInUser)
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/util/Constants.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/util/Constants.kt
new file mode 100644
index 000000000..8dbfe41a0
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/util/Constants.kt
@@ -0,0 +1,10 @@
+package br.com.mdr.testeandroid.util
+
+class Constants {
+ companion object {
+ const val BASE_URL = "https://bank-app-test.herokuapp.com/api/"
+ const val REGEX_SPECIAL_CHARS = "[a-zA-Z0-9]*"
+ const val USER_KEY = "logged_user"
+ const val PREFERENCES_FILE_KEY = "br.com.mdr.testeandroid"
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/util/Mask.kt b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/util/Mask.kt
new file mode 100644
index 000000000..e64351056
--- /dev/null
+++ b/TesteAndroid/app/src/main/java/br/com/mdr/testeandroid/util/Mask.kt
@@ -0,0 +1,39 @@
+package br.com.mdr.testeandroid.util
+
+import android.util.Log
+
+
+class MaskUtil{
+
+ companion object {
+ private const val maskCPF = "###.###.###-##"
+
+ fun removeMask(cpfFull : String) : String{
+ return cpfFull.replace(".", "").replace("-", "")
+ .replace("(", "").replace(")", "")
+ .replace("/", "").replace(" ", "")
+ .replace("*", "")
+ }
+
+ fun getCpfMask(_cpf: String): String {
+ val mask = maskCPF
+ var mascara = ""
+
+ var i = 0
+ for (m : Char in mask.toCharArray()){
+ if ((m != '#') && _cpf.length != i){
+ mascara += m
+ continue
+ }
+ try {
+ mascara += _cpf[i]
+ }catch (e : Exception){
+ break
+ }
+ i++
+ }
+ Log.i("TesteAndroid", mascara)
+ return mascara
+ }
+ }
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/TesteAndroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..2b068d114
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/drawable/error_snack_corner.xml b/TesteAndroid/app/src/main/res/drawable/error_snack_corner.xml
new file mode 100644
index 000000000..3d798bf28
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/drawable/error_snack_corner.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/drawable/ic_launcher_background.xml b/TesteAndroid/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TesteAndroid/app/src/main/res/drawable/ic_logout.png b/TesteAndroid/app/src/main/res/drawable/ic_logout.png
new file mode 100644
index 000000000..de1e4ae3c
Binary files /dev/null and b/TesteAndroid/app/src/main/res/drawable/ic_logout.png differ
diff --git a/TesteAndroid/app/src/main/res/drawable/img_logo.png b/TesteAndroid/app/src/main/res/drawable/img_logo.png
new file mode 100644
index 000000000..66bdc8d5d
Binary files /dev/null and b/TesteAndroid/app/src/main/res/drawable/img_logo.png differ
diff --git a/TesteAndroid/app/src/main/res/layout/activity_main.xml b/TesteAndroid/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..bc2477502
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/layout/dashboard_fragment.xml b/TesteAndroid/app/src/main/res/layout/dashboard_fragment.xml
new file mode 100644
index 000000000..cdd1c3ae7
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/layout/dashboard_fragment.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/layout/dashboard_statement_item.xml b/TesteAndroid/app/src/main/res/layout/dashboard_statement_item.xml
new file mode 100644
index 000000000..849b65214
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/layout/dashboard_statement_item.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/layout/sign_in_fragment.xml b/TesteAndroid/app/src/main/res/layout/sign_in_fragment.xml
new file mode 100644
index 000000000..7d24ec81f
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/layout/sign_in_fragment.xml
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/TesteAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..eca70cfe5
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/TesteAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..eca70cfe5
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png b/TesteAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..a571e6009
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/TesteAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 000000000..61da551c5
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png b/TesteAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..c41dd2853
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/TesteAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 000000000..db5080a75
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/TesteAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..6dba46dab
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/TesteAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..da31a871c
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/TesteAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..15ac68172
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/TesteAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..b216f2d31
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/TesteAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..f25a41974
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/TesteAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/TesteAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..e96783ccc
Binary files /dev/null and b/TesteAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/TesteAndroid/app/src/main/res/navigation/main_nav_graph.xml b/TesteAndroid/app/src/main/res/navigation/main_nav_graph.xml
new file mode 100644
index 000000000..5e2e15a65
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/navigation/main_nav_graph.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/values/colors.xml b/TesteAndroid/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..16f97dd07
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/values/colors.xml
@@ -0,0 +1,13 @@
+
+
+ #FFFFFF
+ #F6F6F6
+ #3B48EE
+
+
+ #FFFFFF
+ #E43342
+ #C5CCD4
+ #485465
+ #A8B4C4
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/values/dimens.xml b/TesteAndroid/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..7398bb774
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/values/dimens.xml
@@ -0,0 +1,20 @@
+
+
+
+ 4dp
+ 8dp
+ 16dp
+ 21dp
+ 28dp
+ 32dp
+
+
+ 16sp
+ 12sp
+ 20sp
+ 25sp
+
+
+ 8dp
+ 2dp
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/values/strings.xml b/TesteAndroid/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..5c6b180d7
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+
+ Teste Android
+ Imagem
+
+
+ Login
+ Entrar com outra conta
+ Acessar conta
+
+
+ User
+ Password
+
+
+ Recents
+ Saldo
+ Conta
+ Agência:
+ Olá,
+ Conta corrente:
+ Bank
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/values/styles.xml b/TesteAndroid/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..8af263db9
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/values/styles.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TesteAndroid/app/src/main/res/xml/backup_descriptor.xml b/TesteAndroid/app/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 000000000..6fd6103a4
--- /dev/null
+++ b/TesteAndroid/app/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/TesteAndroid/app/src/test/java/br/com/mdr/testeandroid/DashboardViewModelTest.kt b/TesteAndroid/app/src/test/java/br/com/mdr/testeandroid/DashboardViewModelTest.kt
new file mode 100644
index 000000000..f9e25901f
--- /dev/null
+++ b/TesteAndroid/app/src/test/java/br/com/mdr/testeandroid/DashboardViewModelTest.kt
@@ -0,0 +1,83 @@
+package br.com.mdr.testeandroid
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import br.com.mdr.testeandroid.di.*
+import br.com.mdr.testeandroid.extensions.getOrAwaitValue
+import br.com.mdr.testeandroid.flow.dashboard.DashboardViewModel
+import br.com.mdr.testeandroid.model.business.User
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.setMain
+import org.junit.*
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.koin.core.context.startKoin
+import org.koin.core.context.stopKoin
+import org.koin.test.KoinTest
+import org.koin.test.inject
+
+
+/**
+ * @author Marlon D. Rocha
+ * @since 10/07/20
+ */
+
+@RunWith(JUnit4::class)
+class DashboardViewModelTest: KoinTest {
+ private val viewModel: DashboardViewModel by inject()
+
+ @get:Rule
+ val rule = InstantTaskExecutorRule()
+
+ @ExperimentalCoroutinesApi
+ @Before
+ fun setup() {
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ startKoin {
+ modules(
+ listOf(
+ apiModule,
+ modelModule,
+ networkModule,
+ repositoryModule,
+ serviceModule,
+ viewModelModule,
+ presenterModule
+ )
+ )
+ }
+ }
+
+ @After
+ fun removeModules() {
+ stopKoin()
+ }
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun `Verify if statements request returns a not empty list`() = runBlockingTest {
+ val user = User(1)
+ viewModel.fetchUserStatements(user)
+
+ Assert.assertEquals(true, viewModel.statementsLive.getOrAwaitValue()?.isNotEmpty())
+ }
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun `Verify if statements request returns a empty list`() = runBlockingTest {
+ val user = User(5)
+ viewModel.fetchUserStatements(user)
+
+ Assert.assertNotEquals(true, viewModel.statementsLive.getOrAwaitValue()?.isEmpty())
+ }
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun `Verify if statements request returns not null object`() = runBlockingTest {
+ val user = User(2)
+ viewModel.fetchUserStatements(user)
+
+ Assert.assertNotNull(viewModel.statementsLive.getOrAwaitValue())
+ }
+
+}
\ No newline at end of file
diff --git a/TesteAndroid/app/src/test/java/br/com/mdr/testeandroid/SignInViewModelTest.kt b/TesteAndroid/app/src/test/java/br/com/mdr/testeandroid/SignInViewModelTest.kt
new file mode 100644
index 000000000..d7b3a651d
--- /dev/null
+++ b/TesteAndroid/app/src/test/java/br/com/mdr/testeandroid/SignInViewModelTest.kt
@@ -0,0 +1,75 @@
+package br.com.mdr.testeandroid
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import br.com.mdr.testeandroid.di.*
+import br.com.mdr.testeandroid.extensions.getOrAwaitValue
+import br.com.mdr.testeandroid.flow.signin.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.setMain
+import org.junit.*
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.koin.core.context.startKoin
+import org.koin.core.context.stopKoin
+import org.koin.test.KoinTest
+import org.koin.test.inject
+
+
+/**
+ * @author Marlon D. Rocha
+ * @since 09/07/20
+ */
+
+@RunWith(JUnit4::class)
+class SignInViewModelTest: KoinTest {
+ private val viewModel: SignInViewModel by inject()
+
+ @get:Rule
+ val rule = InstantTaskExecutorRule()
+
+ @ExperimentalCoroutinesApi
+ @Before
+ fun setup() {
+ Dispatchers.setMain(Dispatchers.Unconfined)
+ startKoin {
+ modules(
+ listOf(
+ apiModule,
+ modelModule,
+ networkModule,
+ repositoryModule,
+ serviceModule,
+ viewModelModule,
+ presenterModule
+ )
+ )
+ }
+ }
+
+ @After
+ fun removeModules() {
+ stopKoin()
+ }
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun `Verify if sign in request returns user`() = runBlockingTest {
+ viewModel.signInHandler.signInPresenter.userName = "email@email.com"
+ viewModel.signInHandler.signInPresenter.password = "M1a."
+ viewModel.callSignIn()
+
+ Assert.assertEquals(true, viewModel.userLive.getOrAwaitValue() != null)
+ }
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun `Verify if sign in request returns error`() = runBlockingTest {
+ viewModel.signInHandler.signInPresenter.userName = ""
+ viewModel.signInHandler.signInPresenter.password = ""
+ viewModel.callSignIn()
+
+ Assert.assertEquals(true, viewModel.errorLive.getOrAwaitValue() != null)
+ }
+
+}
\ No newline at end of file
diff --git a/TesteAndroid/build.gradle b/TesteAndroid/build.gradle
new file mode 100644
index 000000000..6e9f3793f
--- /dev/null
+++ b/TesteAndroid/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = "1.3.72"
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.0.0"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/TesteAndroid/gradle.properties b/TesteAndroid/gradle.properties
new file mode 100644
index 000000000..4d15d015f
--- /dev/null
+++ b/TesteAndroid/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
+# 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/TesteAndroid/gradle/wrapper/gradle-wrapper.jar b/TesteAndroid/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f6b961fd5
Binary files /dev/null and b/TesteAndroid/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/TesteAndroid/gradle/wrapper/gradle-wrapper.properties b/TesteAndroid/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..d3f56fc8d
--- /dev/null
+++ b/TesteAndroid/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Jul 04 16:35:14 BRT 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/TesteAndroid/gradlew b/TesteAndroid/gradlew
new file mode 100755
index 000000000..cccdd3d51
--- /dev/null
+++ b/TesteAndroid/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/TesteAndroid/gradlew.bat b/TesteAndroid/gradlew.bat
new file mode 100644
index 000000000..e95643d6a
--- /dev/null
+++ b/TesteAndroid/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/TesteAndroid/settings.gradle b/TesteAndroid/settings.gradle
new file mode 100644
index 000000000..671ac59fb
--- /dev/null
+++ b/TesteAndroid/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "Teste Android"
\ No newline at end of file
diff --git a/bank_app_layout/.DS_Store b/bank_app_layout/.DS_Store
new file mode 100644
index 000000000..db7fed8b7
Binary files /dev/null and b/bank_app_layout/.DS_Store differ