diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 000000000..67d85fe2c
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1697297474190
+
+
+ 1697297474190
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/.gitignore b/ExtractBank/.gitignore
new file mode 100644
index 000000000..aa724b770
--- /dev/null
+++ b/ExtractBank/.gitignore
@@ -0,0 +1,15 @@
+*.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
+local.properties
diff --git a/ExtractBank/.idea/.gitignore b/ExtractBank/.idea/.gitignore
new file mode 100644
index 000000000..26d33521a
--- /dev/null
+++ b/ExtractBank/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/ExtractBank/.idea/compiler.xml b/ExtractBank/.idea/compiler.xml
new file mode 100644
index 000000000..b589d56e9
--- /dev/null
+++ b/ExtractBank/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/.idea/gradle.xml b/ExtractBank/.idea/gradle.xml
new file mode 100644
index 000000000..ae388c2a5
--- /dev/null
+++ b/ExtractBank/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/.idea/kotlinc.xml b/ExtractBank/.idea/kotlinc.xml
new file mode 100644
index 000000000..fdf8d994a
--- /dev/null
+++ b/ExtractBank/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/.idea/misc.xml b/ExtractBank/.idea/misc.xml
new file mode 100644
index 000000000..8978d23db
--- /dev/null
+++ b/ExtractBank/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/.idea/vcs.xml b/ExtractBank/.idea/vcs.xml
new file mode 100644
index 000000000..6c0b86358
--- /dev/null
+++ b/ExtractBank/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/.gitignore b/ExtractBank/app/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/ExtractBank/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/ExtractBank/app/build.gradle.kts b/ExtractBank/app/build.gradle.kts
new file mode 100644
index 000000000..41bf97541
--- /dev/null
+++ b/ExtractBank/app/build.gradle.kts
@@ -0,0 +1,77 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "fingerfire.com.extractbank"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "fingerfire.com.extractbank"
+ minSdk = 19
+ targetSdk = 33
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ multiDexEnabled = true
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.10.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+
+ //Retrofit
+ implementation("com.squareup.retrofit2:retrofit:2.9.0")
+ implementation("com.squareup.retrofit2:converter-gson:2.9.0")
+
+ //OkHttp
+ implementation("com.squareup.okhttp3:okhttp:4.11.0")
+ implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
+
+ //MVVM e LiveData
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
+
+ //Koin
+ implementation("io.insert-koin:koin-core:3.4.3")
+ implementation("io.insert-koin:koin-android:3.4.3")
+
+ //Mockito
+ testImplementation("org.mockito:mockito-core:5.6.0")
+
+ //Test Kotlin
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("androidx.test:core:1.5.0")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
+ testImplementation("androidx.arch.core:core-testing:2.2.0")
+}
\ No newline at end of file
diff --git a/ExtractBank/app/proguard-rules.pro b/ExtractBank/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/ExtractBank/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/ExtractBank/app/release/app-debug.apk b/ExtractBank/app/release/app-debug.apk
new file mode 100644
index 000000000..f3a66a3f7
Binary files /dev/null and b/ExtractBank/app/release/app-debug.apk differ
diff --git a/ExtractBank/app/src/androidTest/java/fingerfire/com/extractbank/ExampleInstrumentedTest.kt b/ExtractBank/app/src/androidTest/java/fingerfire/com/extractbank/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..de71e8869
--- /dev/null
+++ b/ExtractBank/app/src/androidTest/java/fingerfire/com/extractbank/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package fingerfire.com.extractbank
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * 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("fingerfire.com.extractbank", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/AndroidManifest.xml b/ExtractBank/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..31b53476c
--- /dev/null
+++ b/ExtractBank/app/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/ExtractBankApplication.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/ExtractBankApplication.kt
new file mode 100644
index 000000000..3b4ed0fcc
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/ExtractBankApplication.kt
@@ -0,0 +1,27 @@
+package fingerfire.com.extractbank
+
+import android.app.Application
+import fingerfire.com.extractbank.di.ApiModules
+import fingerfire.com.extractbank.di.DataModule
+import fingerfire.com.extractbank.di.NetworkModules
+import fingerfire.com.extractbank.di.UiModules
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.startKoin
+
+class ExtractBankApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+
+ startKoin {
+ androidContext(this@ExtractBankApplication)
+ modules(
+ listOf(
+ NetworkModules().getNetworkModules(),
+ ApiModules().getApiModules(),
+ UiModules().getViewModules(),
+ DataModule().getDataModules()
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/api/BankApi.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/api/BankApi.kt
new file mode 100644
index 000000000..9aa29044e
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/api/BankApi.kt
@@ -0,0 +1,22 @@
+package fingerfire.com.extractbank.api
+
+import fingerfire.com.extractbank.features.statements.data.StatementsResponse
+import fingerfire.com.extractbank.model.Login
+import fingerfire.com.extractbank.model.User
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+interface BankApi {
+ @POST("api/v1/login")
+ suspend fun login(
+ @Body login: Login
+ ): Response
+
+ @GET("api/v1/login/{loginId}/statement")
+ suspend fun getStatement(
+ @Path("loginId") id: String
+ ): Response>
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/ApiModules.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/ApiModules.kt
new file mode 100644
index 000000000..7fed3df8c
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/ApiModules.kt
@@ -0,0 +1,13 @@
+package fingerfire.com.extractbank.di
+
+import fingerfire.com.extractbank.api.BankApi
+import org.koin.dsl.module
+import retrofit2.Retrofit
+
+class ApiModules {
+ fun getApiModules() = module {
+ factory {
+ get().create(BankApi::class.java)
+ }
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/DataModule.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/DataModule.kt
new file mode 100644
index 000000000..4326cc55c
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/DataModule.kt
@@ -0,0 +1,34 @@
+package fingerfire.com.extractbank.di
+
+import android.app.Application
+import android.content.SharedPreferences
+import fingerfire.com.extractbank.features.login.data.LoginRepository
+import fingerfire.com.extractbank.features.statements.data.StatementRepository
+import org.koin.android.ext.koin.androidApplication
+import org.koin.dsl.module
+
+class DataModule {
+ private fun getSharedPrefs(androidApplication: Application): SharedPreferences {
+ return androidApplication.getSharedPreferences(
+ "default",
+ android.content.Context.MODE_PRIVATE
+ )
+ }
+
+ fun getDataModules() = module {
+ single {
+ getSharedPrefs(androidApplication())
+ }
+
+ single {
+ getSharedPrefs(androidApplication()).edit()
+ }
+ factory {
+ LoginRepository(get(), get())
+ }
+
+ factory {
+ StatementRepository(get())
+ }
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/NetworkModules.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/NetworkModules.kt
new file mode 100644
index 000000000..f6ff5f7cb
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/NetworkModules.kt
@@ -0,0 +1,12 @@
+package fingerfire.com.extractbank.di
+
+import fingerfire.com.extractbank.network.SetupRetrofit
+import org.koin.dsl.module
+
+class NetworkModules {
+ fun getNetworkModules() = module {
+ single {
+ SetupRetrofit.getRetrofit()
+ }
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/UiModules.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/UiModules.kt
new file mode 100644
index 000000000..818094fbf
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/di/UiModules.kt
@@ -0,0 +1,17 @@
+package fingerfire.com.extractbank.di
+
+import fingerfire.com.extractbank.features.login.ui.LoginViewModel
+import fingerfire.com.extractbank.features.statements.ui.StatementViewModel
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
+
+class UiModules {
+ fun getViewModules() = module {
+ viewModel {
+ LoginViewModel(get())
+ }
+ viewModel {
+ StatementViewModel(get())
+ }
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/data/LoginRepository.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/data/LoginRepository.kt
new file mode 100644
index 000000000..3b8d5bcd7
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/data/LoginRepository.kt
@@ -0,0 +1,34 @@
+package fingerfire.com.extractbank.features.login.data
+
+import android.content.SharedPreferences
+import fingerfire.com.extractbank.api.BankApi
+import fingerfire.com.extractbank.model.Login
+import fingerfire.com.extractbank.model.User
+import fingerfire.com.extractbank.network.ServiceState
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class LoginRepository(
+ private val bankApi: BankApi,
+ private val sharedPreferences: SharedPreferences
+) {
+ suspend fun login(login: Login): ServiceState {
+ return withContext(Dispatchers.IO) {
+ val response = bankApi.login(login)
+ if (response.isSuccessful) {
+ saveUser(login)
+ ServiceState.Success(response.body())
+ } else {
+ ServiceState.Error()
+ }
+ }
+ }
+
+ private fun saveUser(login: Login) {
+ sharedPreferences.edit().putString("USER_LOGIN", login.user).apply()
+ }
+
+ fun getSavedUser(): String? {
+ return sharedPreferences.getString("USER_LOGIN", "")
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/LoginActivity.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/LoginActivity.kt
new file mode 100644
index 000000000..97a05bd84
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/LoginActivity.kt
@@ -0,0 +1,89 @@
+package fingerfire.com.extractbank.features.login.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.os.bundleOf
+import fingerfire.com.extractbank.R
+import fingerfire.com.extractbank.databinding.ActivityLoginBinding
+import fingerfire.com.extractbank.features.login.ui.viewstate.LoginViewState
+import fingerfire.com.extractbank.features.statements.ui.StatementActivity
+import fingerfire.com.extractbank.model.Login
+import fingerfire.com.extractbank.model.User
+import fingerfire.com.extractbank.utils.Utils
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+private const val USER = "USER"
+
+class LoginActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityLoginBinding
+ private val viewModel: LoginViewModel by viewModel()
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityLoginBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
+
+ setupLoginButton()
+ observeLoginState()
+ observeSavedUser()
+ }
+
+ private fun observeLoginState() {
+ viewModel.loginLiveData.observe(this) { loginViewState ->
+ when (loginViewState) {
+ is LoginViewState.Success -> {
+ navigateToStatements(loginViewState.user)
+ }
+
+ is LoginViewState.Error -> {
+ Utils.showError(loginViewState.message, this)
+ }
+ }
+ }
+ }
+
+ private fun observeSavedUser() {
+ viewModel.getSavedUserLiveData.observe(this) {
+ if (!it.isNullOrBlank()) {
+ binding.etEmailCpf.setText(it)
+ }
+ }
+ }
+
+ private fun setupLoginButton() {
+ binding.btnLogin.setOnClickListener {
+ binding.progressBar.visibility = View.VISIBLE
+ val emailOrCpf = binding.etEmailCpf.text.toString()
+ val password = binding.etPassword.text.toString()
+
+ val isEmailOrCpfValid = viewModel.isValidEmailOrCPF(emailOrCpf)
+ val (isPasswordValid, passwordErrorMessage) = viewModel.isPasswordValid(password)
+
+ if (isEmailOrCpfValid && isPasswordValid) {
+ val loginData = Login(emailOrCpf, password)
+ viewModel.loginUser(loginData)
+ } else {
+ binding.progressBar.visibility = View.INVISIBLE
+ if (!isEmailOrCpfValid) {
+ Utils.showError(getString(R.string.error_invalid_email_or_cpf), this)
+ } else if (!isPasswordValid) {
+ Utils.showError(
+ passwordErrorMessage ?: getString(R.string.error_password_requirements),
+ this
+ )
+ }
+ }
+ }
+ }
+
+ private fun navigateToStatements(user: User) {
+ val intent = Intent(this, StatementActivity::class.java).apply {
+ putExtras(bundleOf(USER to user))
+ }
+ binding.progressBar.visibility = View.INVISIBLE
+ startActivity(intent)
+ finish()
+ }
+}
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/LoginViewModel.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/LoginViewModel.kt
new file mode 100644
index 000000000..4472afafd
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/LoginViewModel.kt
@@ -0,0 +1,80 @@
+package fingerfire.com.extractbank.features.login.ui
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import fingerfire.com.extractbank.features.login.data.LoginRepository
+import fingerfire.com.extractbank.features.login.ui.viewstate.LoginViewState
+import fingerfire.com.extractbank.model.Login
+import fingerfire.com.extractbank.network.ServiceState
+import fingerfire.com.extractbank.utils.Validations
+import fingerfire.com.extractbank.utils.Validations.isValidEmail
+import kotlinx.coroutines.launch
+
+class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
+ private val loginMutableLiveData: MutableLiveData = MutableLiveData()
+ private val savedUserMutableLiveData: MutableLiveData = MutableLiveData()
+ private val defaultErrorMessage = "Ocorreu um erro durante o login. Tente novamente mais tarde."
+
+ val loginLiveData: LiveData get() = loginMutableLiveData
+ val getSavedUserLiveData: LiveData get() = savedUserMutableLiveData
+
+ init {
+ getUserSaved()
+ }
+
+ private fun getUserSaved() {
+ savedUserMutableLiveData.value = loginRepository.getSavedUser()
+ }
+
+ fun loginUser(login: Login) {
+ val validationResult = validateLogin(login)
+ if (validationResult.isValid) {
+ performLogin(login)
+ } else {
+ handleValidationError(validationResult.errorMessage)
+ }
+ }
+
+ private fun validateLogin(login: Login): ValidationResult {
+ val passwordValidationResult = isPasswordValid(login.password)
+ if (!passwordValidationResult.isValid) {
+ return ValidationResult(false, passwordValidationResult.errorMessage)
+ }
+
+ return ValidationResult(true, null)
+ }
+
+ fun isValidEmailOrCPF(emailCpf: String): Boolean {
+ val cleanedCpf = emailCpf.replace("[.\\-]".toRegex(), "")
+ return emailCpf.isValidEmail() || Validations.isValidCPF(cleanedCpf)
+ }
+
+ fun isPasswordValid(password: String): ValidationResult {
+ val validationResult = Validations.isPasswordValid(password)
+ return ValidationResult(validationResult.isValid, validationResult.errorMessage)
+ }
+
+ private fun performLogin(login: Login) {
+ viewModelScope.launch {
+ when (val loginResponse = loginRepository.login(login)) {
+ is ServiceState.Success -> {
+ loginMutableLiveData.postValue(
+ loginResponse.data?.let { LoginViewState.Success(it) }
+ )
+ }
+
+ is ServiceState.Error -> {
+ handleValidationError(defaultErrorMessage)
+ }
+ }
+ }
+ }
+
+ private fun handleValidationError(errorMessage: String?) {
+ loginMutableLiveData.postValue(LoginViewState.Error(errorMessage ?: defaultErrorMessage))
+ }
+}
+
+data class ValidationResult(val isValid: Boolean, val errorMessage: String?)
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/viewstate/LoginViewState.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/viewstate/LoginViewState.kt
new file mode 100644
index 000000000..c5551f092
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/login/ui/viewstate/LoginViewState.kt
@@ -0,0 +1,8 @@
+package fingerfire.com.extractbank.features.login.ui.viewstate
+
+import fingerfire.com.extractbank.model.User
+
+sealed class LoginViewState {
+ data class Success(val user: User) : LoginViewState()
+ data class Error(val message: String) : LoginViewState()
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/data/StatementRepository.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/data/StatementRepository.kt
new file mode 100644
index 000000000..b61e1dc0a
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/data/StatementRepository.kt
@@ -0,0 +1,15 @@
+package fingerfire.com.extractbank.features.statements.data
+
+import fingerfire.com.extractbank.api.BankApi
+import fingerfire.com.extractbank.network.ServiceState
+
+class StatementRepository(private val bankApi: BankApi) {
+ suspend fun getStatement(idUser: String): ServiceState> {
+ val response = bankApi.getStatement(idUser)
+ return if (response.isSuccessful) {
+ ServiceState.Success(response.body())
+ } else {
+ ServiceState.Error()
+ }
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/data/StatementsResponse.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/data/StatementsResponse.kt
new file mode 100644
index 000000000..d17a2be78
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/data/StatementsResponse.kt
@@ -0,0 +1,9 @@
+package fingerfire.com.extractbank.features.statements.data
+
+data class StatementsResponse(
+ val idUser: String,
+ val date: String,
+ val value: Double,
+ val type: String,
+ val typeTransaction: String
+)
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/StatementActivity.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/StatementActivity.kt
new file mode 100644
index 000000000..1a26f81b6
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/StatementActivity.kt
@@ -0,0 +1,82 @@
+package fingerfire.com.extractbank.features.statements.ui
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import fingerfire.com.extractbank.R
+import fingerfire.com.extractbank.databinding.ActivityStatementBinding
+import fingerfire.com.extractbank.features.login.ui.LoginActivity
+import fingerfire.com.extractbank.features.statements.data.StatementsResponse
+import fingerfire.com.extractbank.features.statements.ui.adapter.StatementAdapter
+import fingerfire.com.extractbank.features.statements.ui.viewstate.StatementViewState
+import fingerfire.com.extractbank.model.User
+import fingerfire.com.extractbank.utils.Utils
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class StatementActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityStatementBinding
+ private lateinit var statementAdapter: StatementAdapter
+ private val viewModel: StatementViewModel by viewModel()
+
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityStatementBinding.inflate(layoutInflater)
+ val view = binding.root
+ setContentView(view)
+ window.statusBarColor = ContextCompat.getColor(this, R.color.blue)
+
+ observeStatement()
+ viewModel.getStatementsForUser("1")
+
+ val extras = intent.extras
+ if (extras != null && extras.containsKey("USER")) {
+ val user = extras.getSerializable("USER") as User
+ loadUser(user)
+ }
+
+ binding.ivLogout.setOnClickListener {
+ val intent = Intent(this, LoginActivity::class.java)
+ startActivity(intent)
+ finish()
+ }
+ }
+
+ private fun loadUser(user: User) {
+ binding.apply {
+ tvName.text = user.name
+ tvBalance.text = Utils.formatToCurrency(user.amount)
+ tvAccount.text = Utils.formatAccountNumber(user.agency, user.account)
+ }
+ }
+
+ private fun observeStatement() {
+ viewModel.statementLiveData.observe(this) { statementViewState ->
+ when (statementViewState) {
+ is StatementViewState.Success -> {
+ setupRecyclerView()
+ initAdapter(statementViewState.data)
+ }
+
+ is StatementViewState.Error -> {
+ Utils.showError(getString(R.string.error_statement_load), this)
+ }
+ }
+ }
+ }
+
+ private fun setupRecyclerView() {
+ binding.rvStatement.layoutManager =
+ LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
+ binding.rvStatement.setHasFixedSize(true)
+ }
+
+ private fun initAdapter(statementsList: List) {
+ statementAdapter = StatementAdapter(statementsList)
+ binding.rvStatement.adapter = statementAdapter
+ }
+}
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/StatementViewModel.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/StatementViewModel.kt
new file mode 100644
index 000000000..4c444b381
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/StatementViewModel.kt
@@ -0,0 +1,39 @@
+package fingerfire.com.extractbank.features.statements.ui
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import fingerfire.com.extractbank.features.statements.data.StatementRepository
+import fingerfire.com.extractbank.features.statements.ui.viewstate.StatementViewState
+import fingerfire.com.extractbank.network.ServiceState
+import kotlinx.coroutines.launch
+
+class StatementViewModel(private val statementRepository: StatementRepository) : ViewModel() {
+ private val statementMutableLiveData: MutableLiveData =
+ MutableLiveData()
+
+ val statementLiveData: LiveData
+ get() = statementMutableLiveData
+
+ fun getStatementsForUser(idUser: String) {
+ viewModelScope.launch {
+ when (val statementsResponse = statementRepository.getStatement(idUser)) {
+ is ServiceState.Success -> {
+ val statements = statementsResponse.data
+ if (statements != null) {
+ if (statements.isNotEmpty()) {
+ statementMutableLiveData.postValue(StatementViewState.Success(statements))
+ } else {
+ statementMutableLiveData.postValue(StatementViewState.Error("Nenhum extrato encontrado."))
+ }
+ }
+ }
+
+ is ServiceState.Error -> {
+ statementMutableLiveData.postValue(StatementViewState.Error("Ocorreu um erro ao obter os extratos."))
+ }
+ }
+ }
+ }
+}
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/adapter/StatementAdapter.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/adapter/StatementAdapter.kt
new file mode 100644
index 000000000..9a3f11bf1
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/adapter/StatementAdapter.kt
@@ -0,0 +1,39 @@
+package fingerfire.com.extractbank.features.statements.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import fingerfire.com.extractbank.databinding.ItemStatementBinding
+import fingerfire.com.extractbank.features.statements.data.StatementsResponse
+import fingerfire.com.extractbank.utils.Utils
+
+class StatementAdapter(
+ private val statementList: List
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatementViewHolder {
+ return StatementViewHolder(
+ ItemStatementBinding
+ .inflate(LayoutInflater.from(parent.context), parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: StatementViewHolder, position: Int) {
+ val statement = statementList[position]
+ with(holder) {
+ binding.tvType.text = statement.type
+ binding.tvValue.text = Utils.formatToCurrency(statement.value)
+ binding.tvDate.text = statement.date
+ binding.tvTypeTransaction.text = statement.typeTransaction
+ }
+ }
+
+
+ override fun getItemCount(): Int {
+ return statementList.size
+ }
+
+ class StatementViewHolder(val binding: ItemStatementBinding) :
+ RecyclerView.ViewHolder(binding.root)
+
+}
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/viewstate/StatementViewState.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/viewstate/StatementViewState.kt
new file mode 100644
index 000000000..f02b79175
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/features/statements/ui/viewstate/StatementViewState.kt
@@ -0,0 +1,8 @@
+package fingerfire.com.extractbank.features.statements.ui.viewstate
+
+import fingerfire.com.extractbank.features.statements.data.StatementsResponse
+
+sealed class StatementViewState {
+ data class Success(val data: List) : StatementViewState()
+ data class Error(val errorMessage: String? = null) : StatementViewState()
+}
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/model/Login.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/model/Login.kt
new file mode 100644
index 000000000..553ef68d6
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/model/Login.kt
@@ -0,0 +1,6 @@
+package fingerfire.com.extractbank.model
+
+data class Login(
+ val user: String,
+ val password: String
+)
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/model/User.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/model/User.kt
new file mode 100644
index 000000000..be1530432
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/model/User.kt
@@ -0,0 +1,11 @@
+package fingerfire.com.extractbank.model
+
+import java.io.Serializable
+
+data class User(
+ val id: Int,
+ val name: String,
+ val account: String,
+ val agency: String,
+ val amount: Double
+) : Serializable
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/network/ServiceState.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/network/ServiceState.kt
new file mode 100644
index 000000000..75ec303ea
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/network/ServiceState.kt
@@ -0,0 +1,6 @@
+package fingerfire.com.extractbank.network
+
+sealed class ServiceState {
+ data class Success(val data: T?) : ServiceState()
+ data class Error(val data: T? = null) : ServiceState()
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/network/SetupRetrofit.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/network/SetupRetrofit.kt
new file mode 100644
index 000000000..8a7f1f8c4
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/network/SetupRetrofit.kt
@@ -0,0 +1,28 @@
+package fingerfire.com.extractbank.network
+
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+object SetupRetrofit {
+
+ private const val BASE_URL = "https://652ac1f24791d884f1fd552e.mockapi.io/"
+
+ private fun client() = OkHttpClient.Builder()
+ .connectTimeout(10, TimeUnit.SECONDS)
+ .readTimeout(10, TimeUnit.SECONDS)
+ .addInterceptor(HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ })
+ .build()
+
+ fun getRetrofit(): Retrofit {
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(client())
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/utils/Utils.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/utils/Utils.kt
new file mode 100644
index 000000000..bcd2d1a71
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/utils/Utils.kt
@@ -0,0 +1,29 @@
+package fingerfire.com.extractbank.utils
+
+import android.content.Context
+import android.widget.Toast
+import java.text.NumberFormat
+import java.util.Locale
+
+object Utils {
+
+ fun formatToCurrency(value: Double): String {
+ val format = NumberFormat.getCurrencyInstance(Locale("pt", "BR"))
+ return format.format(value)
+ }
+
+ fun formatAccountNumber(agencyNumber: String, accountNumber: String): String {
+ val length = accountNumber.length
+ return String.format(
+ "%s / %s.%s-%s",
+ agencyNumber,
+ accountNumber.substring(0, 2),
+ accountNumber.substring(2, length - 2),
+ accountNumber.substring(length - 1, length)
+ )
+ }
+
+ fun showError(errorMessage: String, context: Context) {
+ Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/java/fingerfire/com/extractbank/utils/Validations.kt b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/utils/Validations.kt
new file mode 100644
index 000000000..cc1946805
--- /dev/null
+++ b/ExtractBank/app/src/main/java/fingerfire/com/extractbank/utils/Validations.kt
@@ -0,0 +1,78 @@
+package fingerfire.com.extractbank.utils
+
+object Validations {
+
+ private const val UPPERCASE = "A senha precisa ter ao menos uma letra maiúscula"
+ private const val NUMBER = "A senha precisa ter ao menos um número"
+ private const val SPECIAL_CHAR = "A senha precisa ter ao menos um caractere especial"
+ private const val PASSWORD_ERROR_REQUEST = "A senha precisa ter ao menos um caractere especial"
+ private const val MINIMUM_PASSWORD_LENGTH = "A senha deve ter no mínimo 6 caracteres"
+ private const val MINIMUM_PASSWORD = 6
+
+ fun isValidCPF(cpf: String): Boolean {
+ if (cpf.length != 11) {
+ return false
+ }
+
+ if (cpf.all { it == cpf[0] }) {
+ return false
+ }
+
+ var sum = 0
+ for (i in 0 until 9) {
+ sum += Character.getNumericValue(cpf[i]) * (10 - i)
+ }
+ var remainder = 11 - sum % 11
+ if (remainder == 10 || remainder == 11) {
+ remainder = 0
+ }
+ if (remainder != Character.getNumericValue(cpf[9])) {
+ return false
+ }
+
+ sum = 0
+ for (i in 0 until 10) {
+ sum += Character.getNumericValue(cpf[i]) * (11 - i)
+ }
+ remainder = 11 - sum % 11
+ if (remainder == 10 || remainder == 11) {
+ remainder = 0
+ }
+ if (remainder != Character.getNumericValue(cpf[10])) {
+ return false
+ }
+
+ return true
+ }
+
+ fun String.isValidEmail(): Boolean {
+ val emailPattern = "^[A-Za-z](.*)(@)(.{1,})(\\.)(.{1,})"
+ return matches(emailPattern.toRegex())
+ }
+
+ fun isPasswordValid(password: String): ValidationResult {
+ if (password.length < MINIMUM_PASSWORD) {
+ return ValidationResult(false, MINIMUM_PASSWORD_LENGTH)
+ }
+
+ val hasUppercase = "[A-Z]".toRegex().containsMatchIn(password)
+ val hasNumber = "\\d".toRegex().containsMatchIn(password)
+ val hasSpecialChar =
+ "[!\"#$%&'()*+,-./:;\\\\<=>?@\\[\\]^_`{|}~]".toRegex().containsMatchIn(password)
+
+ return if (hasUppercase && hasNumber && hasSpecialChar) {
+ ValidationResult(true, null)
+ } else {
+ val errorMessage = when {
+ !hasUppercase -> UPPERCASE
+ !hasNumber -> NUMBER
+ !hasSpecialChar -> SPECIAL_CHAR
+ else -> PASSWORD_ERROR_REQUEST
+ }
+ ValidationResult(false, errorMessage)
+ }
+ }
+
+ data class ValidationResult(val isValid: Boolean, val errorMessage: String? = "")
+
+ }
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/ExtractBank/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..2b068d114
--- /dev/null
+++ b/ExtractBank/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/drawable/bg_button_login.xml b/ExtractBank/app/src/main/res/drawable/bg_button_login.xml
new file mode 100644
index 000000000..4730e4736
--- /dev/null
+++ b/ExtractBank/app/src/main/res/drawable/bg_button_login.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/drawable/bg_edittext_login.xml b/ExtractBank/app/src/main/res/drawable/bg_edittext_login.xml
new file mode 100644
index 000000000..ca2b66eb3
--- /dev/null
+++ b/ExtractBank/app/src/main/res/drawable/bg_edittext_login.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/ExtractBank/app/src/main/res/drawable/ic_launcher_background.xml b/ExtractBank/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/ExtractBank/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ExtractBank/app/src/main/res/drawable/logo_bank.png b/ExtractBank/app/src/main/res/drawable/logo_bank.png
new file mode 100644
index 000000000..66bdc8d5d
Binary files /dev/null and b/ExtractBank/app/src/main/res/drawable/logo_bank.png differ
diff --git a/ExtractBank/app/src/main/res/drawable/logout_icon.png b/ExtractBank/app/src/main/res/drawable/logout_icon.png
new file mode 100644
index 000000000..de1e4ae3c
Binary files /dev/null and b/ExtractBank/app/src/main/res/drawable/logout_icon.png differ
diff --git a/ExtractBank/app/src/main/res/layout/activity_login.xml b/ExtractBank/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 000000000..720db5040
--- /dev/null
+++ b/ExtractBank/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/layout/activity_statement.xml b/ExtractBank/app/src/main/res/layout/activity_statement.xml
new file mode 100644
index 000000000..5508e472d
--- /dev/null
+++ b/ExtractBank/app/src/main/res/layout/activity_statement.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/layout/item_statement.xml b/ExtractBank/app/src/main/res/layout/item_statement.xml
new file mode 100644
index 000000000..3581159f1
--- /dev/null
+++ b/ExtractBank/app/src/main/res/layout/item_statement.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ExtractBank/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..6f3b755bf
--- /dev/null
+++ b/ExtractBank/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ExtractBank/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..6f3b755bf
--- /dev/null
+++ b/ExtractBank/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/ExtractBank/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/ExtractBank/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/ExtractBank/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/ExtractBank/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/ExtractBank/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/ExtractBank/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/ExtractBank/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/ExtractBank/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/ExtractBank/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/ExtractBank/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/ExtractBank/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/ExtractBank/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/ExtractBank/app/src/main/res/values/colors.xml b/ExtractBank/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..af9af5a16
--- /dev/null
+++ b/ExtractBank/app/src/main/res/values/colors.xml
@@ -0,0 +1,8 @@
+
+
+ #FF000000
+ #FFFFFFFF
+ #0000FF
+ #eeeeee
+ #bcbcbc
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/values/dimens.xml b/ExtractBank/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..0ea82da25
--- /dev/null
+++ b/ExtractBank/app/src/main/res/values/dimens.xml
@@ -0,0 +1,13 @@
+
+
+ 13dp
+ 13dp
+ 10dp
+ 10dp
+ 48dp
+ 13dp
+ 13dp
+ 30dp
+ 60dp
+ 90dp
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/values/strings.xml b/ExtractBank/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..af7cf7527
--- /dev/null
+++ b/ExtractBank/app/src/main/res/values/strings.xml
@@ -0,0 +1,14 @@
+
+ ExtractBank
+ Email ou Cpf
+ Senha
+ Login
+
+ Email ou CPF não está em um formato válido
+ A senha deve ter no mínimo 6 caracteres
+ A senha deve conter pelo menos uma letra maiúscula
+ A senha deve conter pelo menos um número
+ A senha deve conter pelo menos um caractere especial
+ A senha não atende aos requisitos
+ Ocorreu um erro ao obter os extratos.
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/values/themes.xml b/ExtractBank/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..9b737f9c7
--- /dev/null
+++ b/ExtractBank/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/xml/backup_rules.xml b/ExtractBank/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..fa0f996d2
--- /dev/null
+++ b/ExtractBank/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/main/res/xml/data_extraction_rules.xml b/ExtractBank/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..9ee9997b0
--- /dev/null
+++ b/ExtractBank/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ExtractBank/app/src/test/java/fingerfire/com/extractbank/LoginViewModelTest.kt b/ExtractBank/app/src/test/java/fingerfire/com/extractbank/LoginViewModelTest.kt
new file mode 100644
index 000000000..46f0913f2
--- /dev/null
+++ b/ExtractBank/app/src/test/java/fingerfire/com/extractbank/LoginViewModelTest.kt
@@ -0,0 +1,80 @@
+package fingerfire.com.extractbank
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import fingerfire.com.extractbank.features.login.data.LoginRepository
+import fingerfire.com.extractbank.features.login.ui.LoginViewModel
+import fingerfire.com.extractbank.features.login.ui.viewstate.LoginViewState
+import fingerfire.com.extractbank.model.Login
+import fingerfire.com.extractbank.model.User
+import fingerfire.com.extractbank.network.ServiceState
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class LoginViewModelTest {
+
+ @get:Rule
+ val rule = InstantTaskExecutorRule()
+
+ @Mock
+ private lateinit var loginRepository: LoginRepository
+
+ @Mock
+ private lateinit var observer: Observer
+
+ private lateinit var loginViewModel: LoginViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+ loginViewModel = LoginViewModel(loginRepository)
+ loginViewModel.loginLiveData.observeForever(observer)
+ }
+
+ @Test
+ fun loginUserSuccess() = runTest {
+ val testDispatcher = UnconfinedTestDispatcher(testScheduler)
+ Dispatchers.setMain(testDispatcher)
+ // Given
+ val login = Login("testUser", "123456M@R")
+ val user = User(1, "Test User", "12345", "6789", 1000.0)
+ `when`(loginRepository.login(login)).thenReturn(ServiceState.Success(user))
+ `when`(loginRepository.getSavedUser()).thenReturn(" ")
+
+ // When
+ loginViewModel.loginUser(login)
+
+ // Then
+ verify(loginRepository).login(login)
+ verify(observer).onChanged(LoginViewState.Success(user))
+ }
+
+ @Test
+ fun loginUserError() = runTest {
+ val testDispatcher = UnconfinedTestDispatcher(testScheduler)
+ Dispatchers.setMain(testDispatcher)
+ val user = User(1, "Test User", "12345", "6789", 1000.0)
+ // Given
+ val login = Login("testUser", "123456M@R")
+ val errorMessage = "Ocorreu um erro durante o login. Tente novamente mais tarde."
+ `when`(loginRepository.login(login)).thenReturn(ServiceState.Error(user))
+
+ // When
+ loginViewModel.loginUser(login)
+
+ // Then
+ verify(loginRepository).login(login)
+ verify(observer).onChanged(LoginViewState.Error(errorMessage))
+ }
+}
diff --git a/ExtractBank/app/src/test/java/fingerfire/com/extractbank/StatementViewModelTest.kt b/ExtractBank/app/src/test/java/fingerfire/com/extractbank/StatementViewModelTest.kt
new file mode 100644
index 000000000..36ed92b10
--- /dev/null
+++ b/ExtractBank/app/src/test/java/fingerfire/com/extractbank/StatementViewModelTest.kt
@@ -0,0 +1,98 @@
+package fingerfire.com.extractbank
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.Observer
+import fingerfire.com.extractbank.features.statements.data.StatementRepository
+import fingerfire.com.extractbank.features.statements.data.StatementsResponse
+import fingerfire.com.extractbank.features.statements.ui.StatementViewModel
+import fingerfire.com.extractbank.features.statements.ui.viewstate.StatementViewState
+import fingerfire.com.extractbank.network.ServiceState
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatementViewModelTest {
+
+ @get:Rule
+ val rule = InstantTaskExecutorRule()
+
+ @Mock
+ private lateinit var statementRepository: StatementRepository
+
+ @Mock
+ private lateinit var observer: Observer
+
+ private lateinit var statementViewModel: StatementViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+ statementViewModel = StatementViewModel(statementRepository)
+ statementViewModel.statementLiveData.observeForever(observer)
+ }
+
+ @Test
+ fun getStatementsForUserSuccess() = runTest {
+ val testDispatcher = UnconfinedTestDispatcher(testScheduler)
+ Dispatchers.setMain(testDispatcher)
+ // Given
+ val idUser = "123"
+ val statements = listOf(
+ StatementsResponse(idUser, "2023-01-01", 100.0, "Credit", "Deposit"),
+ StatementsResponse(idUser, "2023-01-02", 50.0, "Debit", "Withdrawal")
+ )
+ `when`(statementRepository.getStatement(idUser)).thenReturn(ServiceState.Success(statements))
+
+ // When
+ statementViewModel.getStatementsForUser(idUser)
+
+ // Then
+ verify(statementRepository).getStatement(idUser)
+ verify(observer).onChanged(StatementViewState.Success(statements))
+ }
+
+ @Test
+ fun getStatementsForUserEmpty() = runTest {
+ val testDispatcher = UnconfinedTestDispatcher(testScheduler)
+ Dispatchers.setMain(testDispatcher)
+ // Given
+ val idUser = "123"
+ val statements = emptyList()
+ `when`(statementRepository.getStatement(idUser)).thenReturn(ServiceState.Success(statements))
+
+ // When
+ statementViewModel.getStatementsForUser(idUser)
+
+ // Then
+ verify(statementRepository).getStatement(idUser)
+ verify(observer).onChanged(StatementViewState.Error("Nenhum extrato encontrado."))
+ }
+
+ @Test
+ fun getStatementsForUserError() = runTest {
+ val testDispatcher = UnconfinedTestDispatcher(testScheduler)
+ Dispatchers.setMain(testDispatcher)
+ val statements = emptyList()
+ // Given
+ val idUser = "123"
+ val errorMessage = "Ocorreu um erro ao obter os extratos."
+ `when`(statementRepository.getStatement(idUser)).thenReturn(ServiceState.Error(statements))
+
+ // When
+ statementViewModel.getStatementsForUser(idUser)
+
+ // Then
+ verify(statementRepository).getStatement(idUser)
+ verify(observer).onChanged(StatementViewState.Error(errorMessage))
+ }
+}
\ No newline at end of file
diff --git a/ExtractBank/build.gradle.kts b/ExtractBank/build.gradle.kts
new file mode 100644
index 000000000..df06d4444
--- /dev/null
+++ b/ExtractBank/build.gradle.kts
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id("com.android.application") version "8.1.2" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.0" apply false
+}
\ No newline at end of file
diff --git a/ExtractBank/gradle.properties b/ExtractBank/gradle.properties
new file mode 100644
index 000000000..3c5031eb7
--- /dev/null
+++ b/ExtractBank/gradle.properties
@@ -0,0 +1,23 @@
+# 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
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/ExtractBank/gradle/wrapper/gradle-wrapper.jar b/ExtractBank/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..e708b1c02
Binary files /dev/null and b/ExtractBank/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/ExtractBank/gradle/wrapper/gradle-wrapper.properties b/ExtractBank/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..54fc47d0d
--- /dev/null
+++ b/ExtractBank/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Oct 14 12:33:42 BRT 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/ExtractBank/gradlew b/ExtractBank/gradlew
new file mode 100755
index 000000000..4f906e0c8
--- /dev/null
+++ b/ExtractBank/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## 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='"-Xmx64m" "-Xms64m"'
+
+# 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 or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
+
+exec "$JAVACMD" "$@"
diff --git a/ExtractBank/gradlew.bat b/ExtractBank/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/ExtractBank/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@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 Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@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="-Xmx64m" "-Xms64m"
+
+@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 execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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/ExtractBank/settings.gradle.kts b/ExtractBank/settings.gradle.kts
new file mode 100644
index 000000000..3d05a586b
--- /dev/null
+++ b/ExtractBank/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "ExtractBank"
+include(":app")
+
\ No newline at end of file
diff --git a/README.md b/README.md
index bd73feb5f..e375da398 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,17 @@
-# Show me the code
-
-Esse repositório contem todo o material necessário para realizar o teste:
-- A especificação do layout está na pasta 'bank_app_layout' abrindo o index.html, utilizar os Styles do Android
-
-- Os dados da Api estão mockados, os exemplos e a especificação dos serviços (login e statements) se encontram no arquivo BankApp.postman_collection.json ( é necessário instalar o postman e importar a colection https://www.getpostman.com/apps)
+### Tecnologias Principais:
+
+* Retrofit: Biblioteca recomendada pela Google para consumir APIs.
+* GSON: Biblioteca da Google para lidar com dados no formato JSON.
+* Coroutines: Usadas para executar código de forma assíncrona, especialmente no consumo da API.
+* LiveData: Utilizada para observar e atualizar os dados na interface do usuário.
+* Koin: Framework de injeção de dependências.
+* Arquitetura MVVM: Seguindo o padrão Model-View-ViewModel para uma organização mais eficiente do código.
+* Clean Code: Adoção de práticas de programação limpa para manter o código legível e fácil de dar manutenção.
+* JUnit: Usado para testes unitários.
+* Mockito: Usado para criação de mocks em testes unitários.

-### # DESAFIO:
-
-Na primeira tela teremos um formulario de login, o campo user deve aceitar email ou cpf,
-o campo password deve validar se a senha tem pelo menos uma letra maiuscula, um caracter especial e um caracter alfanumérico.
-Apos a validação, realizar o login no endpoint https://bank-app-test.herokuapp.com/api/login e exibir os dados de retorno na próxima tela.
-O ultimo usuário logado deve ser salvo de forma segura localmente, e exibido na tela de login se houver algum salvo.
-
-Na segunda tela será exibido os dados formatados do retorno do login e será necessário fazer um segundo request para obter os lançamentos do usuário, no endpoint https://bank-app-test.herokuapp.com/api/statements/{idUser} que retornará uma lista de lançamentos
-
-### # Avaliação
-
-Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura do app. É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits.
-
-Obrigatórios:
-
-* Java ou Kotlin
-* Material Design
-* O app deve funcionar a partir do android 4.4
-* Testes unitários, pode usar a ferramenta que você tem mais experiência, só nos explique o que ele tem de bom.
-* Arquitetura a ser utilizada: Android Clean Code (https://github.com/kmmraj/android-clean-code && https://medium.com/@kmmraj/android-clean-code-part-1-c66da6551d1)
-* Uso do git.
-
-### # Observações gerais
-
-Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto.
-Pedimos que trabalhe sozinho e não divulgue o resultado na internet.
-
-Faça um fork desse desse repositório em seu Github e ao finalizar nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando.
-
-# Importante: não há prazo de entrega, faça com qualidade!
+### Apk do projeto para executar
-# BOA SORTE!
+[⬇️ APK](https://github.com/marlonsantini/TesteAndroidv2/tree/master/ExtractBank/app/release)