diff --git a/SantanderApp/.gitignore b/SantanderApp/.gitignore
new file mode 100644
index 000000000..b1123a006
--- /dev/null
+++ b/SantanderApp/.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
+/.idea/
diff --git a/SantanderApp/README.md b/SantanderApp/README.md
new file mode 100644
index 000000000..4941733f9
--- /dev/null
+++ b/SantanderApp/README.md
@@ -0,0 +1,8 @@
+#Teste Raphael Castro Martin
+
+## Executando o projeto
+Para testar o aplicativo basta abrir o diretório SantanderApp com o Android Studio, selecionar o dispositivo que irá executar o aplicativo no AVD Manager, e no menu Run clicar em Run...
+
+## Executando testes unitários
+Para executar os testes unitários e obter relatório de cobertura de código basta clicar com o botão direito na pasta do módulo app no Android Studio e clicar em Run 'All Tests' with Coverage
+
diff --git a/SantanderApp/app/.gitignore b/SantanderApp/app/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/SantanderApp/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/SantanderApp/app/build.gradle b/SantanderApp/app/build.gradle
new file mode 100644
index 000000000..d0cc04ada
--- /dev/null
+++ b/SantanderApp/app/build.gradle
@@ -0,0 +1,53 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ applicationId "com.qintess.santanderapp"
+ 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'
+ }
+ }
+
+ testOptions {
+ unitTests.includeAndroidResources = true
+ unitTests {
+ returnDefaultValues = true
+ includeAndroidResources = true
+ }
+ }
+
+}
+
+dependencies {
+ // Libs do app
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.core:core-ktx:1.3.0'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation("com.squareup.okhttp3:okhttp:4.7.2")
+
+ // Libs de teste
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ testImplementation 'org.robolectric:robolectric:4.3'
+ testImplementation 'androidx.test:core:1.2.0'
+ testImplementation 'org.mockito:mockito-core:1.10.19'
+ testImplementation 'org.json:json:20180813'
+}
diff --git a/SantanderApp/app/proguard-rules.pro b/SantanderApp/app/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/SantanderApp/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/SantanderApp/app/src/main/AndroidManifest.xml b/SantanderApp/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..1eeefc7af
--- /dev/null
+++ b/SantanderApp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Formatter.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Formatter.kt
new file mode 100644
index 000000000..9a58fe7bb
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Formatter.kt
@@ -0,0 +1,51 @@
+package com.qintess.santanderapp.helper
+
+import android.annotation.SuppressLint
+import java.math.RoundingMode
+import java.text.DateFormat
+import java.text.DecimalFormat
+import java.text.NumberFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+object Formatter {
+ fun formatMoney(value: Double): String {
+ val formatter = getMoneyFormatter()
+ return formatter.format(value)
+ }
+
+ fun formatDate(strDate: String): String {
+ val toDateFormatter = getDateFormatterStringToDate()
+ val toStringFormatter = getDateFormatterDateToString()
+
+ val date = toDateFormatter.parse(strDate)
+ return toStringFormatter.format(date)
+ }
+
+ private fun getMoneyFormatter(): DecimalFormat {
+ val nf = NumberFormat.getInstance(Locale.US) as DecimalFormat
+ nf.roundingMode = RoundingMode.HALF_UP
+ nf.minimumFractionDigits = 2
+ nf.maximumFractionDigits = 2
+
+ val symbols = nf.decimalFormatSymbols
+ symbols.groupingSeparator = '.'
+ symbols.decimalSeparator = ','
+ nf.decimalFormatSymbols = symbols
+
+ nf.positivePrefix = "R$ "
+ nf.negativePrefix = "R$ -"
+
+ return nf
+ }
+
+ @SuppressLint("SimpleDateFormat")
+ private fun getDateFormatterStringToDate(): DateFormat {
+ return SimpleDateFormat("yyyy-MM-dd")
+ }
+
+ @SuppressLint("SimpleDateFormat")
+ private fun getDateFormatterDateToString(): DateFormat {
+ return SimpleDateFormat("dd/MM/yyyy")
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/JsonHelper.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/JsonHelper.kt
new file mode 100644
index 000000000..8bcfedce0
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/JsonHelper.kt
@@ -0,0 +1,10 @@
+package com.qintess.santanderapp.helper
+
+import org.json.JSONArray
+import org.json.JSONObject
+
+fun JSONArray.forEach(callback: (JSONObject) -> Unit) {
+ for (i in 0 until this.length()) {
+ callback(this.getJSONObject(i))
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Prefs.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Prefs.kt
new file mode 100644
index 000000000..7f6243ab7
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Prefs.kt
@@ -0,0 +1,29 @@
+package com.qintess.santanderapp.helper
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.qintess.santanderapp.BuildConfig
+
+class Prefs(ctx: Context) {
+ enum class Key(val value: String?) {
+ LastUser("LAST_USER")
+ }
+
+ private val PREFS_FILE_NAME = BuildConfig.APPLICATION_ID
+ private var sharedPreferences: SharedPreferences
+
+ init {
+ sharedPreferences = ctx.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE)
+ }
+
+ fun putString(key: Key, value: String) {
+ with(sharedPreferences.edit()) {
+ putString(key.value, value)
+ commit()
+ }
+ }
+
+ fun getString(key: Key): String? {
+ return sharedPreferences.getString(key.value, null)
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Validator.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Validator.kt
new file mode 100644
index 000000000..5a17641cd
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/helper/Validator.kt
@@ -0,0 +1,65 @@
+package com.qintess.santanderapp.helper
+
+class Validator {
+ companion object {
+ const val LOGIN_TITLE_ERROR = "Não foi possível realizar o login"
+
+ const val CREDENTIALS_TITLE_ERROR = "Credenciais inválidas"
+ const val USER_ERROR = "Preencha um usuário válido. O usuário deve ser um CPF ou e-mail"
+ const val PASS_ERROR = "Preencha uma senha válida. A senha deve conter uma letra maiúsula, um caractere especial e um número."
+
+ const val STATEMENTS_TITLE_ERROR = "Não foi possível buscar os lançamentos"
+
+ fun isEmailValid(email: String): Boolean {
+ val pattern = "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,3}".toRegex()
+ return email.matches(pattern)
+ }
+
+ fun isCpfValid(cpf: String): Boolean {
+ val mask = Regex("[.-]")
+ val unmasked_cpf = mask.replace(cpf, "")
+
+ if (unmasked_cpf.length != 11) {
+ return false
+ }
+
+ val numbers = arrayListOf()
+
+ unmasked_cpf.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 isPasswordValid(password: String): Boolean {
+ if (
+ !password.matches(".*[A-Z].*".toRegex()) ||
+ !password.matches(".*[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?].*".toRegex()) ||
+ !password.matches(".*[0-9].*".toRegex())
+ ) {
+ return false
+ }
+
+ return true
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/CredentialsModel.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/CredentialsModel.kt
new file mode 100644
index 000000000..c2c8b4c89
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/CredentialsModel.kt
@@ -0,0 +1,3 @@
+package com.qintess.santanderapp.model
+
+data class CredentialsModel(val user: String, val password: String)
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/StatementModel.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/StatementModel.kt
new file mode 100644
index 000000000..ec06c2761
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/StatementModel.kt
@@ -0,0 +1,12 @@
+package com.qintess.santanderapp.model
+
+enum class StatementType(val value: String) {
+ Debit("Pagamento"),
+ Credit("Recebimento")
+}
+
+data class StatementModel(val title: String,
+ val desc: String,
+ val date: String,
+ val value: Double,
+ val type: StatementType)
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/UserModel.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/UserModel.kt
new file mode 100644
index 000000000..aa1721e63
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/model/UserModel.kt
@@ -0,0 +1,38 @@
+package com.qintess.santanderapp.model
+
+import android.os.Parcel
+import android.os.Parcelable
+
+data class UserModel (val userId: Int,
+ val name: String,
+ val bankAccount: String,
+ val agency: String,
+ val balance: Double): Parcelable {
+ constructor(parcel: Parcel) : this(
+ parcel.readInt(),
+ parcel.readString() ?: "",
+ parcel.readString() ?: "",
+ parcel.readString() ?: "",
+ parcel.readDouble()
+ ) {
+ }
+
+ override fun writeToParcel(dest: Parcel?, flags: Int) {
+ dest?.writeInt(userId)
+ dest?.writeString(name)
+ dest?.writeString(bankAccount)
+ dest?.writeString(agency)
+ dest?.writeDouble(balance)
+ }
+ override fun describeContents(): Int { return 0 }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): UserModel {
+ return UserModel(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/Http.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/Http.kt
new file mode 100644
index 000000000..fdf51a809
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/Http.kt
@@ -0,0 +1,95 @@
+package com.qintess.santanderapp.service
+
+import android.os.Handler
+import android.util.Log
+import okhttp3.MediaType
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+import org.json.JSONObject
+
+typealias RequestParameters = HashMap
+typealias SuccessCallback = (T) -> Unit
+typealias FailureCallback = (Exception) -> Unit
+
+interface ServiceInterface {
+ fun getHttpClient(): HttpInterface
+}
+
+interface HttpInterface {
+ fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback)
+ fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback)
+ fun get(url: String, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ get(url, RequestParameters(), onSuccess, onFailure)
+ }
+}
+
+class Http: HttpInterface {
+ private val TAG = this::class.java.name
+ private val API_URL = "https://bank-app-test.herokuapp.com/api/"
+ private val JSON: MediaType = "application/json; charset=utf-8".toMediaType()
+ private val client = OkHttpClient()
+
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ try {
+ val stringifiedBody = JSONObject(bodyParameters).toString()
+ val body = stringifiedBody.toRequestBody(JSON)
+ val request = Request.Builder()
+ .url(API_URL + url)
+ .post(body)
+ .build()
+
+ val handler = Handler()
+
+ Thread {
+ val response = client.newCall(request).execute()
+
+ if (response.body != null) {
+ handler.post {
+ onSuccess(JSONObject(response.body!!.string()))
+ }
+ } else {
+ val errosMsg = "Resposta do servidor veio vazia"
+ Log.e(TAG, errosMsg)
+ handler.post {
+ onFailure(Exception(errosMsg))
+ }
+ }
+
+ }.start()
+ } catch (e: Exception) {
+ onFailure(e)
+ }
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ try {
+ val request = Request.Builder()
+ .url(API_URL + url)
+ .get()
+ .build()
+
+ val handler = Handler()
+
+ Thread {
+ val response = client.newCall(request).execute()
+
+ if (response.body != null) {
+ handler.post {
+ onSuccess(JSONObject(response.body!!.string()))
+ }
+ } else {
+ val errosMsg = "Resposta do servidor veio vazia"
+ Log.e(TAG, errosMsg)
+ handler.post {
+ onFailure(Exception(errosMsg))
+ }
+ }
+
+ }.start()
+ } catch (e: Exception) {
+ onFailure(e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/StatementsService.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/StatementsService.kt
new file mode 100644
index 000000000..3165b8dfb
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/StatementsService.kt
@@ -0,0 +1,34 @@
+package com.qintess.santanderapp.service
+
+import com.qintess.santanderapp.helper.forEach
+import com.qintess.santanderapp.model.StatementModel
+import com.qintess.santanderapp.model.StatementType
+
+open class StatementsService: ServiceInterface {
+ override fun getHttpClient(): HttpInterface {
+ return Http()
+ }
+
+ fun getStatements(userId: Int, onSuccess: SuccessCallback>, onFailure: FailureCallback) {
+ getHttpClient().get("statements/$userId",
+ onSuccess = {
+ val statements = ArrayList()
+ var jsonArray = it.getJSONArray("statementList")
+ jsonArray.forEach {
+ val value = it.getDouble("value")
+ statements.add(
+ StatementModel(
+ title = it.getString("title"),
+ desc = it.getString("desc"),
+ date = it.getString("date"),
+ value = if (value >= 0) value else value * -1,
+ type = if (value >= 0) StatementType.Credit else StatementType.Debit
+ )
+ )
+ }
+ onSuccess(statements)
+ },
+ onFailure = onFailure
+ )
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/UserService.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/UserService.kt
new file mode 100644
index 000000000..f2ce8dfa3
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/service/UserService.kt
@@ -0,0 +1,32 @@
+package com.qintess.santanderapp.service
+
+import com.qintess.santanderapp.model.CredentialsModel
+import com.qintess.santanderapp.model.UserModel
+
+open class UserService: ServiceInterface {
+ override fun getHttpClient(): HttpInterface {
+ return Http()
+ }
+
+ fun login(credentials: CredentialsModel, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ val bodyParameters = RequestParameters()
+ bodyParameters["user"] = credentials.user
+ bodyParameters["password"] = credentials.password
+
+ getHttpClient().post(
+ "login", bodyParameters,
+ onSuccess = { responseJsonObj ->
+ val userJsonObj = responseJsonObj.getJSONObject("userAccount")
+ val user = UserModel(
+ userId = userJsonObj.getInt("userId"),
+ name = userJsonObj.getString("name"),
+ bankAccount = userJsonObj.getString("bankAccount"),
+ agency = userJsonObj.getString("agency"),
+ balance = userJsonObj.getDouble("balance")
+ )
+ onSuccess(user)
+ },
+ onFailure = onFailure
+ )
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/PasswordField.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/PasswordField.kt
new file mode 100644
index 000000000..8ea52df41
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/PasswordField.kt
@@ -0,0 +1,9 @@
+package com.qintess.santanderapp.ui.components
+
+import android.content.Context
+import android.util.AttributeSet
+import com.qintess.santanderapp.helper.Validator
+
+class PasswordField(ctx: Context, attrs: AttributeSet) : ValidatorEditText(ctx, attrs) {
+ override var rule = {value: String -> Validator.isPasswordValid(value) }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/StatementsAdapter.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/StatementsAdapter.kt
new file mode 100644
index 000000000..2922fe7c5
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/StatementsAdapter.kt
@@ -0,0 +1,64 @@
+package com.qintess.santanderapp.ui.components
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.TextView
+import com.qintess.santanderapp.R
+import com.qintess.santanderapp.helper.Formatter
+import com.qintess.santanderapp.model.StatementModel
+import kotlinx.android.synthetic.main.list_item_statement.view.*
+
+class StatementsAdapter(ctx: Context, private val statements: ArrayList): BaseAdapter() {
+ private val inflater: LayoutInflater = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ @SuppressLint("ViewHolder")
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
+ val holder: ViewHolder
+ val view: View
+
+ if (convertView == null) {
+ view = inflater.inflate(R.layout.list_item_statement, parent, false)
+ holder = ViewHolder()
+ holder.type = view.type
+ holder.date = view.date
+ holder.description = view.description
+ holder.value = view.value
+ view.tag = holder
+ } else {
+ view = convertView
+ holder = view.tag as ViewHolder
+ }
+
+
+ val statement = getItem(position)
+ holder.type?.text = statement.type.value
+ holder.date?.text = Formatter.formatDate(statement.date)
+ holder.description?.text = statement.desc
+ holder.value?.text = Formatter.formatMoney(statement.value)
+
+ return view
+ }
+
+ override fun getItem(position: Int): StatementModel {
+ return statements[position]
+ }
+
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ override fun getCount(): Int {
+ return statements.size
+ }
+
+ class ViewHolder {
+ var type: TextView? = null
+ var date: TextView? = null
+ var description: TextView? = null
+ var value: TextView? = null
+ }
+
+}
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/UserField.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/UserField.kt
new file mode 100644
index 000000000..d319c69be
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/UserField.kt
@@ -0,0 +1,9 @@
+package com.qintess.santanderapp.ui.components
+
+import android.content.Context
+import android.util.AttributeSet
+import com.qintess.santanderapp.helper.Validator
+
+class UserField(ctx: Context, attrs: AttributeSet) : ValidatorEditText(ctx, attrs) {
+ override var rule = { value: String -> Validator.isEmailValid(value) || Validator.isCpfValid(value) }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/ValidatorField.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/ValidatorField.kt
new file mode 100644
index 000000000..f62527d2e
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/components/ValidatorField.kt
@@ -0,0 +1,14 @@
+package com.qintess.santanderapp.ui.components
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatEditText
+
+abstract class ValidatorEditText(ctx: Context, attrs: AttributeSet): AppCompatEditText(ctx, attrs) {
+ // Propriedade que define a regra de validação, onde o parâmetro value é o texto atual do campo, e o retorno da lambda é um booleano que diz se o valor passado é válido
+ protected open var rule: (value: String) -> Boolean = { true }
+
+ // Computed val que retorna se campo é válido
+ val valid: Boolean
+ get() = this.rule(this.text.toString())
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/AppActivity.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/AppActivity.kt
new file mode 100644
index 000000000..261b963d6
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/AppActivity.kt
@@ -0,0 +1,17 @@
+package com.qintess.santanderapp.ui.view
+
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+
+abstract class AppActivity : AppCompatActivity() {
+ fun showAlert(title: String, msg: String): Boolean {
+ val alertDialog = AlertDialog.Builder(this).create()
+ alertDialog.setTitle(title)
+ alertDialog.setMessage(msg)
+ alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK") { dialog, _ ->
+ dialog.dismiss()
+ }
+ alertDialog.show()
+ return true // somente para saber que toda execução acima aconteceu sem erros
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginActivity.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginActivity.kt
new file mode 100644
index 000000000..16ebd57d4
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginActivity.kt
@@ -0,0 +1,62 @@
+package com.qintess.santanderapp.ui.view.loginScreen
+
+import android.content.Intent
+import android.os.Bundle
+import com.qintess.santanderapp.R
+import com.qintess.santanderapp.helper.Prefs
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.ui.view.AppActivity
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsActivity
+import kotlinx.android.synthetic.main.activity_login.*
+
+interface LoginActivityInput {
+ fun createListeners()
+ fun checkLastUser()
+ fun displayLastUser(username: String)
+ fun login()
+ fun showAlert(title: String, msg: String): Boolean
+ fun goToStatements(user: UserModel)
+}
+
+class LoginActivity : AppActivity(), LoginActivityInput {
+ var output: LoginInteractorInput? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_login)
+
+ LoginConfigurator.INSTANCE.configure(this)
+ checkLastUser()
+ createListeners()
+ }
+
+ override fun createListeners() {
+ loginButton.setOnClickListener {
+ login()
+ }
+ }
+
+ override fun checkLastUser() {
+ val username = Prefs(this).getString(Prefs.Key.LastUser)
+ output?.checkLastUser(username)
+ }
+
+ override fun displayLastUser(username: String) {
+ usernameField.setText(username)
+ }
+
+ override fun login() {
+ val loginRequest = LoginRequest()
+ loginRequest.username = usernameField
+ loginRequest.password = passwordField
+ output?.login(loginRequest)
+ }
+
+ override fun goToStatements(user: UserModel) {
+ Prefs(this).putString(Prefs.Key.LastUser, usernameField.text.toString())
+ val intent = Intent(this, StatementsActivity::class.java).apply {
+ putExtra("user", user)
+ }
+ startActivity(intent)
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginConfigurator.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginConfigurator.kt
new file mode 100644
index 000000000..96fac9889
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginConfigurator.kt
@@ -0,0 +1,19 @@
+package com.qintess.santanderapp.ui.view.loginScreen
+
+import java.lang.ref.WeakReference
+
+enum class LoginConfigurator {
+ INSTANCE;
+
+ fun configure(activity: LoginActivity) {
+ val presenter = LoginPresenter()
+ presenter.output = WeakReference(activity)
+
+ val interactor = LoginInteractor()
+ interactor.output = presenter
+
+ if (activity.output == null) {
+ activity.output = interactor
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginInteractor.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginInteractor.kt
new file mode 100644
index 000000000..184ed22af
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginInteractor.kt
@@ -0,0 +1,59 @@
+package com.qintess.santanderapp.ui.view.loginScreen
+
+import android.util.Log
+import com.qintess.santanderapp.helper.Validator
+import com.qintess.santanderapp.model.CredentialsModel
+import com.qintess.santanderapp.service.UserService
+
+interface LoginInteractorInput {
+ fun login(request: LoginRequest)
+ fun auth(credentials: CredentialsModel)
+ fun checkLastUser(username: String?)
+ fun getCredentialsError(credentials: LoginRequest): String?
+}
+
+open class LoginInteractor: LoginInteractorInput {
+ var output: LoginPresenterInput? = null
+ open var userService = UserService()
+ private val handler = android.os.Handler()
+
+ val TAG = this.javaClass.name
+
+ override fun login(request: LoginRequest) {
+ val errorMsg = getCredentialsError(request)
+ if (errorMsg == null) {
+ val credentials = CredentialsModel(request.username?.text.toString(), request.password?.text.toString())
+ auth(credentials)
+ } else {
+ output?.presentErrorMessage(Validator.CREDENTIALS_TITLE_ERROR, errorMsg)
+ }
+ }
+
+ override fun auth(credentials: CredentialsModel) {
+ userService.login(credentials,
+ onSuccess = {
+ output?.presentStatementScreen(it)
+ },
+ onFailure = {
+ Log.e(TAG, it.message ?: "Erro ao realizar login")
+ output?.presentErrorMessage(Validator.LOGIN_TITLE_ERROR, "Tente novamente mais tarde")
+ }
+ )
+ }
+
+ override fun checkLastUser(username: String?) {
+ if (username != null) {
+ output?.presentLastUser(username)
+ }
+ }
+
+ override fun getCredentialsError(credentials: LoginRequest): String? {
+ if (!credentials.username?.valid!!) {
+ return Validator.USER_ERROR
+ } else if (!credentials.password?.valid!!) {
+ return Validator.PASS_ERROR
+ }
+ return null
+ }
+
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginPresenter.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginPresenter.kt
new file mode 100644
index 000000000..c4f750b28
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginPresenter.kt
@@ -0,0 +1,26 @@
+package com.qintess.santanderapp.ui.view.loginScreen
+
+import com.qintess.santanderapp.model.UserModel
+import java.lang.ref.WeakReference
+
+interface LoginPresenterInput {
+ fun presentLastUser(username: String)
+ fun presentErrorMessage(title: String, msg: String)
+ fun presentStatementScreen(user: UserModel)
+}
+
+class LoginPresenter: LoginPresenterInput {
+ var output: WeakReference? = null
+
+ override fun presentLastUser(username: String) {
+ output?.get()?.displayLastUser(username)
+ }
+
+ override fun presentErrorMessage(title: String, msg: String) {
+ output?.get()?.showAlert(title, msg)
+ }
+
+ override fun presentStatementScreen(user: UserModel) {
+ output?.get()?.goToStatements(user)
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginRequest.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginRequest.kt
new file mode 100644
index 000000000..d21bd2472
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/loginScreen/LoginRequest.kt
@@ -0,0 +1,9 @@
+package com.qintess.santanderapp.ui.view.loginScreen
+
+import com.qintess.santanderapp.ui.components.PasswordField
+import com.qintess.santanderapp.ui.components.UserField
+
+class LoginRequest {
+ var username: UserField? = null
+ var password: PasswordField? = null
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsActivity.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsActivity.kt
new file mode 100644
index 000000000..c4a9055c6
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsActivity.kt
@@ -0,0 +1,66 @@
+package com.qintess.santanderapp.ui.view.statementsScreen
+
+import android.content.Intent
+import android.os.Bundle
+import com.qintess.santanderapp.R
+import com.qintess.santanderapp.helper.Formatter
+import com.qintess.santanderapp.model.StatementModel
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.ui.components.StatementsAdapter
+import com.qintess.santanderapp.ui.view.AppActivity
+import com.qintess.santanderapp.ui.view.loginScreen.LoginActivity
+import kotlinx.android.synthetic.main.activity_statements.*
+
+interface StatementsActivityInput {
+ fun displayStatements(userId: Int)
+ fun updateList(statements: ArrayList)
+ fun showAlert(title: String, msg: String): Boolean
+ fun createListeners()
+ fun logout()
+}
+
+class StatementsActivity : AppActivity(), StatementsActivityInput {
+ var output: StatementsInteractorInput? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_statements)
+
+ StatementsConfigurator.INSTANCE.configure(this)
+
+ createListeners()
+ val user = intent.getParcelableExtra("user")
+ user?.let {
+ displayUser(user)
+ displayStatements(user.userId)
+ }
+ }
+
+ override fun createListeners() {
+ logoutButton.setOnClickListener {
+ logout()
+ }
+ }
+
+ override fun logout() {
+ val intent = Intent(this, LoginActivity::class.java)
+ startActivity(intent)
+ }
+
+ fun displayUser(user: UserModel) {
+ userNameField.text = user.name
+ agencyAndAccount.text = String.format(getString(R.string.agencyAndAccount), user.agency, user.bankAccount)
+ balance.text = Formatter.formatMoney(user.balance)
+ }
+
+ override fun displayStatements(userId: Int) {
+ output?.fetchStatements(userId)
+ }
+
+ override fun updateList(statements: ArrayList) {
+ val adapter = StatementsAdapter(this, statements)
+ statements_list.divider = null
+ statements_list.dividerHeight = 20
+ statements_list.adapter = adapter
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsConfigurator.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsConfigurator.kt
new file mode 100644
index 000000000..5e1ff7ad6
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsConfigurator.kt
@@ -0,0 +1,19 @@
+package com.qintess.santanderapp.ui.view.statementsScreen
+
+import java.lang.ref.WeakReference
+
+enum class StatementsConfigurator {
+ INSTANCE;
+
+ fun configure(activity: StatementsActivity) {
+ val presenter = StatementsPresenter()
+ presenter.output = WeakReference(activity)
+
+ val interactor = StatementsInteractor()
+ interactor.output = presenter
+
+ if (activity.output == null) {
+ activity.output = interactor
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsInteractor.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsInteractor.kt
new file mode 100644
index 000000000..f3a1b2020
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsInteractor.kt
@@ -0,0 +1,24 @@
+package com.qintess.santanderapp.ui.view.statementsScreen
+
+import com.qintess.santanderapp.helper.Validator
+import com.qintess.santanderapp.service.StatementsService
+
+interface StatementsInteractorInput {
+ fun fetchStatements(user_id: Int)
+}
+
+open class StatementsInteractor: StatementsInteractorInput {
+ var output: StatementsPresenterInput? = null
+ open var statementsService = StatementsService()
+
+ override fun fetchStatements(user_id: Int) {
+ statementsService?.getStatements(user_id,
+ onSuccess = {
+ output?.presentStatements(it)
+ },
+ onFailure = {
+ output?.presentErrorMessage(Validator.STATEMENTS_TITLE_ERROR, "Tente novamente mais tarde")
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsPresenter.kt b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsPresenter.kt
new file mode 100644
index 000000000..9765ee58d
--- /dev/null
+++ b/SantanderApp/app/src/main/java/com/qintess/santanderapp/ui/view/statementsScreen/StatementsPresenter.kt
@@ -0,0 +1,21 @@
+package com.qintess.santanderapp.ui.view.statementsScreen
+
+import com.qintess.santanderapp.model.StatementModel
+import java.lang.ref.WeakReference
+
+interface StatementsPresenterInput {
+ fun presentStatements(statements: ArrayList)
+ fun presentErrorMessage(title: String, msg: String)
+}
+
+class StatementsPresenter: StatementsPresenterInput {
+ var output: WeakReference? = null
+
+ override fun presentStatements(statements: ArrayList) {
+ output?.get()?.updateList(statements)
+ }
+
+ override fun presentErrorMessage(title: String, msg: String) {
+ output?.get()?.showAlert(title, msg)
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/SantanderApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..2b068d114
--- /dev/null
+++ b/SantanderApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/drawable/ic_launcher_background.xml b/SantanderApp/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..07d5da9cb
--- /dev/null
+++ b/SantanderApp/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderApp/app/src/main/res/drawable/logo.png b/SantanderApp/app/src/main/res/drawable/logo.png
new file mode 100644
index 000000000..66bdc8d5d
Binary files /dev/null and b/SantanderApp/app/src/main/res/drawable/logo.png differ
diff --git a/SantanderApp/app/src/main/res/drawable/logout_icon.png b/SantanderApp/app/src/main/res/drawable/logout_icon.png
new file mode 100644
index 000000000..de1e4ae3c
Binary files /dev/null and b/SantanderApp/app/src/main/res/drawable/logout_icon.png differ
diff --git a/SantanderApp/app/src/main/res/drawable/shape_button.xml b/SantanderApp/app/src/main/res/drawable/shape_button.xml
new file mode 100644
index 000000000..1fde0ea33
--- /dev/null
+++ b/SantanderApp/app/src/main/res/drawable/shape_button.xml
@@ -0,0 +1,9 @@
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/drawable/shape_cell.xml b/SantanderApp/app/src/main/res/drawable/shape_cell.xml
new file mode 100644
index 000000000..105347f7a
--- /dev/null
+++ b/SantanderApp/app/src/main/res/drawable/shape_cell.xml
@@ -0,0 +1,9 @@
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/drawable/shape_field.xml b/SantanderApp/app/src/main/res/drawable/shape_field.xml
new file mode 100644
index 000000000..2e32b5def
--- /dev/null
+++ b/SantanderApp/app/src/main/res/drawable/shape_field.xml
@@ -0,0 +1,10 @@
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/layout/activity_login.xml b/SantanderApp/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 000000000..b014d9fe0
--- /dev/null
+++ b/SantanderApp/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/layout/activity_statements.xml b/SantanderApp/app/src/main/res/layout/activity_statements.xml
new file mode 100644
index 000000000..b6652ca54
--- /dev/null
+++ b/SantanderApp/app/src/main/res/layout/activity_statements.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/layout/list_item_statement.xml b/SantanderApp/app/src/main/res/layout/list_item_statement.xml
new file mode 100644
index 000000000..1ac35c3a3
--- /dev/null
+++ b/SantanderApp/app/src/main/res/layout/list_item_statement.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/SantanderApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..eca70cfe5
--- /dev/null
+++ b/SantanderApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/SantanderApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..eca70cfe5
--- /dev/null
+++ b/SantanderApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/mipmap-hdpi/ic_launcher.png b/SantanderApp/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..a571e6009
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/SantanderApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 000000000..61da551c5
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-mdpi/ic_launcher.png b/SantanderApp/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..c41dd2853
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/SantanderApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 000000000..db5080a75
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/SantanderApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..6dba46dab
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/SantanderApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..da31a871c
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/SantanderApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..15ac68172
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/SantanderApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..b216f2d31
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/SantanderApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..f25a41974
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/SantanderApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/SantanderApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 000000000..e96783ccc
Binary files /dev/null and b/SantanderApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/SantanderApp/app/src/main/res/values/colors.xml b/SantanderApp/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..550529aac
--- /dev/null
+++ b/SantanderApp/app/src/main/res/values/colors.xml
@@ -0,0 +1,9 @@
+
+
+ #3B48EE
+ #485465
+ #A8B4C4
+ #DCE2EE
+ #FEFEFE
+ #FFFFFF
+
diff --git a/SantanderApp/app/src/main/res/values/dimens.xml b/SantanderApp/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..f8a2d1d94
--- /dev/null
+++ b/SantanderApp/app/src/main/res/values/dimens.xml
@@ -0,0 +1,10 @@
+
+
+ 125dp
+ 75dp
+
+ 4dp
+ 50dp
+ 20dp
+ 12dp
+
\ No newline at end of file
diff --git a/SantanderApp/app/src/main/res/values/strings.xml b/SantanderApp/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..1bda80d1c
--- /dev/null
+++ b/SantanderApp/app/src/main/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+ SantanderApp
+ Logo do Bank
+ Login
+ User
+ Password
+ Conta
+ Saldo
+ Recentes
+ %s / %s
+ R$ %s
+
diff --git a/SantanderApp/app/src/main/res/values/styles.xml b/SantanderApp/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..bae06a730
--- /dev/null
+++ b/SantanderApp/app/src/main/res/values/styles.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/FormatterTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/FormatterTest.kt
new file mode 100644
index 000000000..1414f6ff0
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/FormatterTest.kt
@@ -0,0 +1,29 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import com.qintess.santanderapp.helper.Formatter
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class FormatterTest {
+ // formatMoney deve retornar valor formatado
+ @Test
+ fun formatMoney_shouldReturnFormattedValue() {
+ val positiveValue = 1234.56
+ val negativeValue = -1234.56
+
+ Assert.assertEquals("R$ 1.234,56", Formatter.formatMoney(positiveValue))
+ Assert.assertEquals("R$ -1.234,56", Formatter.formatMoney(negativeValue))
+ }
+
+ // formatDate deve retornar data formata
+ @Test
+ fun formatDate_shouldReturnFormattedDate() {
+ Assert.assertEquals("20/10/2020", Formatter.formatDate("2020-10-20"))
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginActivityTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginActivityTest.kt
new file mode 100644
index 000000000..301b1b77c
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginActivityTest.kt
@@ -0,0 +1,279 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import androidx.test.core.app.ActivityScenario.launch
+import com.qintess.santanderapp.helper.Validator
+import com.qintess.santanderapp.model.CredentialsModel
+import com.qintess.santanderapp.ui.view.loginScreen.LoginActivity
+import com.qintess.santanderapp.ui.view.loginScreen.LoginInteractor
+import com.qintess.santanderapp.ui.view.loginScreen.LoginInteractorInput
+import com.qintess.santanderapp.ui.view.loginScreen.LoginRequest
+import kotlinx.android.synthetic.main.activity_login.*
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+@LooperMode(LooperMode.Mode.PAUSED) // FIXME: Remover quando issue for resolvida (https://github.com/robolectric/robolectric/issues/5356)
+class LoginActivityTest {
+ // LoginActivity não é null
+ @Test
+ fun loginActivity_isNotNull() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ Assert.assertNotNull(activity)
+ }
+ }
+
+ // usernameField não é null
+ @Test
+ fun user_field_isNotNull() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ Assert.assertNotNull(activity.usernameField)
+ }
+ }
+
+ // passwordField não é null
+ @Test
+ fun pass_field_isNotNul() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ Assert.assertNotNull(activity.passwordField)
+ }
+ }
+
+ // usernameField está validando e-mail
+ @Test
+ fun user_field_isValidatingEmail() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val userField = activity.usernameField
+
+ //E-mail válido
+ userField.setText("raphael@email.com")
+ Assert.assertTrue(userField.valid)
+
+ // E-mails inválidos
+ userField.setText("raphael@email")
+ Assert.assertFalse(userField.valid)
+
+ userField.setText("raphael.com")
+ Assert.assertFalse(userField.valid)
+
+ userField.setText("**@**.com")
+ Assert.assertFalse(userField.valid)
+
+ userField.setText("")
+ Assert.assertFalse(userField.valid)
+ }
+ }
+
+ // usernameField está validando CPF
+ @Test
+ fun user_field_isValidatingCPF() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val userField = activity.usernameField
+
+ // CPF válido
+ userField.setText("373.213.858-50")
+ Assert.assertTrue(userField.valid)
+
+ // CPFs inválidos
+ userField.setText("000.000.000-00")
+ Assert.assertFalse(userField.valid)
+
+ userField.setText("1234")
+ Assert.assertFalse(userField.valid)
+
+ userField.setText("ABC")
+ Assert.assertFalse(userField.valid)
+
+ userField.setText("")
+ Assert.assertFalse(userField.valid)
+ }
+ }
+
+ // passwordField está validando
+ @Test
+ fun pass_field_isValidating() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val passwordField = activity.passwordField
+
+ // Senha válida
+ passwordField.setText("AppSantander123@")
+ Assert.assertTrue(passwordField.valid)
+
+ //Senhas inválidas
+ passwordField.setText("appsantander123@")
+ Assert.assertFalse(passwordField.valid)
+ passwordField.setText("AppSantander123")
+ Assert.assertFalse(passwordField.valid)
+ passwordField.setText("AppSantander@")
+ Assert.assertFalse(passwordField.valid)
+ passwordField.setText("")
+ }
+ }
+
+ // No onCreate da activiy deve-se chamar checkLastUser
+ @Test
+ fun onCreate_shouldCallCheckLastUser() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val loginActivityOutputSpy = LoginActivityOutputSpy()
+ activity.output = loginActivityOutputSpy
+
+ activity.checkLastUser()
+
+ Assert.assertTrue(loginActivityOutputSpy.checkLastUserIsCalled)
+ }
+ }
+
+ // No login deve-se chamar validação
+ @Test
+ fun onLogin_shouldCallValidation() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val loginActivityOutputSpy = LoginInteractorInputSpy()
+ activity.output = loginActivityOutputSpy
+
+ activity.login()
+
+ Assert.assertTrue(loginActivityOutputSpy.validationMethodIsCalled)
+ }
+ }
+
+ @Test
+ fun getCredentialsErro_isValid() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val interactor = LoginInteractor()
+ val request = LoginRequest()
+
+ // Credenciais válidas com e-mail
+ activity.usernameField.setText("raphael@email.com")
+ activity.passwordField.setText("SantanderApp@1")
+ request.username = activity.usernameField
+ request.password = activity.passwordField
+ Assert.assertNull(interactor.getCredentialsError(request))
+
+ //Credenciais válidas com CPF
+ activity.usernameField.setText("373.213.858-50")
+ request.username = activity.usernameField
+ Assert.assertNull(interactor.getCredentialsError(request))
+
+ //Credenciais inválidas com e-mail inválido
+ activity.usernameField.setText("raphael@email")
+ request.username = activity.usernameField
+ Assert.assertEquals(interactor.getCredentialsError(request), Validator.USER_ERROR)
+
+ //Credenciais inválidas com CPF inválido
+ activity.usernameField.setText("000.000.000-00")
+ request.username = activity.usernameField
+ Assert.assertEquals(interactor.getCredentialsError(request), Validator.USER_ERROR)
+
+ // Credenciais inválidas com e-mail válido e senha inválida
+ activity.usernameField.setText("raphael@email.com")
+ activity.passwordField.setText("1234")
+ request.username = activity.usernameField
+ request.password = activity.passwordField
+ Assert.assertEquals(interactor.getCredentialsError(request), Validator.PASS_ERROR)
+
+ // Credenciais inválidas com CPF válido e senha inválida
+ activity.usernameField.setText("373.213.858-50")
+ activity.passwordField.setText("1234")
+ request.username = activity.usernameField
+ request.password = activity.passwordField
+ Assert.assertEquals(interactor.getCredentialsError(request), Validator.PASS_ERROR)
+ }
+ }
+
+ // Na chamada do login, os valores dos campos são passados corretamente
+ @Test
+ fun onCallLogin_fieldsValuesArePassedCorrectly() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val loginActivityOutputSpy = LoginActivityOutputSpy()
+ activity.output = loginActivityOutputSpy
+
+ activity.usernameField.setText("raphael@email.com")
+ activity.passwordField.setText("SantanderApp@1")
+
+ activity.login()
+
+ Assert.assertEquals(activity.usernameField.text.toString(), loginActivityOutputSpy.loginRequestCopy?.username?.text.toString())
+ Assert.assertEquals(activity.passwordField.text.toString(), loginActivityOutputSpy.loginRequestCopy?.password?.text.toString())
+ }
+ }
+
+ // Na chamada do login com credenciais válidas é chamado auth
+ @Test
+ fun onCallLogin_authIsCalled() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val loginActivityOutputSpy = LoginActivityOutputAuthSpy()
+ activity.output = loginActivityOutputSpy
+
+ activity.usernameField.setText("raphael@email.com")
+ activity.passwordField.setText("SantanderApp@1")
+
+ activity.login()
+
+ Assert.assertTrue(loginActivityOutputSpy.authIsCalled)
+ }
+ }
+
+ // Ao clicar no botão Login deve chamar método login()
+ @Test
+ fun onButtonClick_shouldCallLoginMethod() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val loginActivityOutputSpy = LoginActivityOutputSpy()
+ activity.output = loginActivityOutputSpy
+ activity.createListeners()
+ activity.loginButton.performClick()
+ Assert.assertTrue(loginActivityOutputSpy.loginIsCalled)
+ }
+ }
+
+ // showText deve mostrar dialog
+ @Test
+ fun onShowText_dialogIsShown() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ Assert.assertTrue(activity.showAlert(Validator.CREDENTIALS_TITLE_ERROR, Validator.USER_ERROR))
+ }
+ }
+
+ class LoginActivityOutputSpy: LoginInteractorInput {
+ var loginIsCalled = false
+ var loginRequestCopy: LoginRequest? = null
+ var checkLastUserIsCalled = false
+
+ override fun login(request: LoginRequest) {
+ loginIsCalled = true
+ loginRequestCopy = request
+ }
+
+ override fun auth(credentials: CredentialsModel) { return }
+
+ override fun checkLastUser(username: String?) {
+ checkLastUserIsCalled = true
+ }
+
+ override fun getCredentialsError(credentials: LoginRequest): String? {
+ return null
+ }
+ }
+
+ class LoginActivityOutputAuthSpy: LoginInteractor() {
+ var authIsCalled = false
+ var credentialsCopy: CredentialsModel? = null
+
+ override fun auth(credentials: CredentialsModel) {
+ credentialsCopy = credentials
+ authIsCalled = true
+ }
+ }
+
+ class LoginInteractorInputSpy: LoginInteractor() {
+ var validationMethodIsCalled = false
+ override fun getCredentialsError(credentials: LoginRequest): String? {
+ validationMethodIsCalled = true
+ return super.getCredentialsError(credentials)
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginInteractorTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginInteractorTest.kt
new file mode 100644
index 000000000..dfcae66cf
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginInteractorTest.kt
@@ -0,0 +1,138 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import com.qintess.santanderapp.helper.Validator
+import com.qintess.santanderapp.model.CredentialsModel
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.service.*
+import com.qintess.santanderapp.ui.view.loginScreen.LoginInteractor
+import com.qintess.santanderapp.ui.view.loginScreen.LoginPresenterInput
+import com.qintess.santanderapp.ui.view.loginScreen.LoginRequest
+import org.json.JSONObject
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.runners.MockitoJUnitRunner
+import org.robolectric.annotation.Config
+
+@RunWith(MockitoJUnitRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class LoginInteractorTest {
+ @Mock
+ private lateinit var interactor: LoginInteractor
+ @Mock
+ private lateinit var userService: UserService
+
+ // Checagem de último usuário logado com usuário armazenado deve mostrar último usuário no campo
+ @Test
+ fun checkLastUser_with_stored_user_shouldCallPresentLastUser() {
+ interactor = LoginInteractor()
+
+ val loginInteractorOutputSpy = LoginInteractorOutputSpy()
+ interactor.output = loginInteractorOutputSpy
+ interactor.checkLastUser("raphacmartin")
+
+ Assert.assertTrue(loginInteractorOutputSpy.presentLastUserIsCalled)
+ }
+
+ // No erro de credenciais deve-se mostrar mensagem de erro
+ @Test
+ fun onCredentialsError_shouldPresentLoginErrorMessage() {
+ val credentials = LoginRequest()
+ `when`(interactor.getCredentialsError(credentials))
+ .thenReturn(Validator.USER_ERROR)
+ `when`(interactor.login(credentials))
+ .thenCallRealMethod()
+ val loginInteractorOutputSpy = LoginInteractorOutputSpy()
+ interactor.output = loginInteractorOutputSpy
+ interactor.login(credentials)
+
+ Assert.assertTrue(loginInteractorOutputSpy.presentLoginErrorMsgIsCalled)
+ }
+
+ // Em login com sucesso deve-se mostrar a tela de lançamentos
+ @Test
+ fun onSuccessfulLogin_shouldPresentStatementsScreen() {
+ `when`(userService.getHttpClient())
+ .thenReturn(FakeSuccessHttpClient())
+ interactor = LoginInteractor()
+ interactor.userService = userService
+
+ val loginInteractorOutputSpy = LoginInteractorOutputSpy()
+ interactor.output = loginInteractorOutputSpy
+
+ val credentials = CredentialsModel("raphael@email.com", "Santander@1")
+ interactor.auth(credentials)
+
+ Assert.assertTrue(loginInteractorOutputSpy.presentStatementScreenIsCalled)
+ }
+
+ // Em login com falha deve-se mostrar mensagem de erro
+ @Test
+ fun onFailureLogin_shouldPresentErrorMessage() {
+ `when`(userService.getHttpClient())
+ .thenReturn(FakeErrorHttpClient())
+ interactor = LoginInteractor()
+ interactor.userService = userService
+
+ val loginInteractorOutputSpy = LoginInteractorOutputSpy()
+ interactor.output = loginInteractorOutputSpy
+
+ val credentials = CredentialsModel("raphael@email.com", "Santander@1")
+ interactor.auth(credentials)
+
+ Assert.assertTrue(loginInteractorOutputSpy.presentLoginErrorMsgIsCalled)
+ }
+
+ class LoginInteractorOutputSpy: LoginPresenterInput {
+ var presentLastUserIsCalled = false
+ var presentLoginErrorMsgIsCalled = false
+ var presentStatementScreenIsCalled = false
+ override fun presentLastUser(username: String) {
+ presentLastUserIsCalled = true
+ }
+
+ override fun presentErrorMessage(title: String, msg: String) {
+ presentLoginErrorMsgIsCalled = true
+ }
+
+ override fun presentStatementScreen(user: UserModel) {
+ presentStatementScreenIsCalled = true
+ }
+ }
+
+ open class FakeSuccessHttpClient: HttpInterface {
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onSuccess(
+ JSONObject("""
+ {
+ "userAccount": {
+ "userId": 1,
+ "name": "Jose da Silva Teste",
+ "bankAccount": "2050",
+ "agency": "012314564",
+ "balance": 3.3445
+ },
+ "error": {}
+ }
+ """)
+ )
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(Exception("Nenhum método get para usuários"))
+ }
+ }
+
+ open class FakeErrorHttpClient: HttpInterface {
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(java.lang.Exception("Erro ao realizar login"))
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(Exception("Nenhum método get para usuários"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginPresenterTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginPresenterTest.kt
new file mode 100644
index 000000000..060d50dbe
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/LoginPresenterTest.kt
@@ -0,0 +1,93 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import androidx.test.core.app.ActivityScenario.launch
+import com.qintess.santanderapp.helper.Validator
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.ui.view.loginScreen.LoginActivity
+import com.qintess.santanderapp.ui.view.loginScreen.LoginActivityInput
+import com.qintess.santanderapp.ui.view.loginScreen.LoginPresenter
+import kotlinx.android.synthetic.main.activity_login.*
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import java.lang.ref.WeakReference
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class LoginPresenterTest {
+ // Ao chamar presentLastUser deve-se chamar displayLastUser
+ @Test
+ fun onPresentLastUser_shouldCallDisplayLastUser(){
+ val presenter = LoginPresenter()
+ val loginPresenterOutputSpy = LoginPresenterOutputSpy()
+ presenter.output = WeakReference(loginPresenterOutputSpy)
+ presenter.presentLastUser("raphacmartin")
+
+ Assert.assertTrue(loginPresenterOutputSpy.displayLastUserIsCalled)
+ }
+
+ // Ao chamar presentLastUser deve-se exibir o usuário correto no campo
+ @Test
+ fun onPresentLastUser_shouldSetCorrectTextInField() {
+ launch(LoginActivity::class.java).onActivity { activity ->
+ val presenter = LoginPresenter()
+ presenter.output = WeakReference(activity)
+ val username = "raphacmartin"
+ presenter.presentLastUser(username)
+
+ Assert.assertEquals(username, activity.usernameField.text.toString())
+ }
+ }
+
+ // Ao chamar presentLoginErrorMessage deve-se chamar showAlert
+ @Test
+ fun onPresentLoginErrorMessage_shouldCallShowAlert() {
+ val presenter = LoginPresenter()
+ val loginPresenterOutputSpy = LoginPresenterOutputSpy()
+ presenter.output = WeakReference(loginPresenterOutputSpy)
+ presenter.presentErrorMessage(Validator.CREDENTIALS_TITLE_ERROR, Validator.USER_ERROR)
+
+ Assert.assertTrue(loginPresenterOutputSpy.showAlertIsCalled)
+ }
+
+ // Ao chamar presentStatementScreen deve-se chamar goToStatements
+ @Test
+ fun onPresentStatementScreen_shouldCallGoToStatements() {
+ val presenter = LoginPresenter()
+ val loginPresenterOutputSpy = LoginPresenterOutputSpy()
+ presenter.output = WeakReference(loginPresenterOutputSpy)
+ presenter.presentStatementScreen(UserModel(1, "Raphael", "000", "000", 100.0))
+
+ Assert.assertTrue(loginPresenterOutputSpy.goToStatementsIsCalled)
+ }
+
+
+ class LoginPresenterOutputSpy: LoginActivityInput {
+ var displayLastUserIsCalled = false
+ var showAlertIsCalled = false
+ var goToStatementsIsCalled = false
+ override fun createListeners() { return }
+
+ override fun checkLastUser() { return }
+
+ override fun displayLastUser(username: String) {
+ displayLastUserIsCalled = true
+ return
+ }
+
+ override fun login() { return }
+
+ override fun showAlert(title: String, msg: String): Boolean {
+ showAlertIsCalled = true
+ return false
+ }
+
+ override fun goToStatements(user: UserModel) {
+ goToStatementsIsCalled = true
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/PrefsTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/PrefsTest.kt
new file mode 100644
index 000000000..8be8ff332
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/PrefsTest.kt
@@ -0,0 +1,33 @@
+package com.qintess.santanderapp
+
+import android.app.Application
+import android.os.Build
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.qintess.santanderapp.helper.Prefs
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class PrefsTest {
+ // Prefs não é null
+ @Test
+ fun prefs_isNotNull() {
+ val ctx = getApplicationContext()
+ Assert.assertNotNull(Prefs(ctx))
+ }
+
+ // putString salva valor corretamente
+ @Test
+ fun putString_isSavingCorrectValue() {
+ val ctx = getApplicationContext()
+ val prefs = Prefs(ctx)
+ val last_username = "raphacmartin"
+
+ prefs.putString(Prefs.Key.LastUser, last_username)
+ Assert.assertEquals(prefs.getString(Prefs.Key.LastUser), last_username)
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsActivityTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsActivityTest.kt
new file mode 100644
index 000000000..2ffa877e5
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsActivityTest.kt
@@ -0,0 +1,40 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import androidx.test.core.app.ActivityScenario.launch
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsActivity
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsInteractorInput
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class StatementsActivityTest {
+ private val userModel = UserModel(1, "Raphael", "000", "000", 100.0)
+
+ // displayStatements deve chamar fetchStatements
+ @Test
+ fun displayStatements_shouldCallFetchStatements() {
+ launch(StatementsActivity::class.java).onActivity { activity ->
+ val statementsActivityOutputSpy = StatementsActivityOutputSpy()
+ activity.output = statementsActivityOutputSpy
+
+ activity.displayStatements(userModel.userId)
+
+ Assert.assertTrue(statementsActivityOutputSpy.fetchStatementsIsCalled)
+ }
+ }
+
+ class StatementsActivityOutputSpy: StatementsInteractorInput {
+ var fetchStatementsIsCalled = false
+
+ override fun fetchStatements(user_id: Int) {
+ fetchStatementsIsCalled = true
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsInteractorTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsInteractorTest.kt
new file mode 100644
index 000000000..adfd42b40
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsInteractorTest.kt
@@ -0,0 +1,114 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import com.qintess.santanderapp.model.StatementModel
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.service.*
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsInteractor
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsPresenterInput
+import org.json.JSONObject
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.runners.MockitoJUnitRunner
+import org.robolectric.annotation.Config
+
+@RunWith(MockitoJUnitRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class StatementsInteractorTest {
+ private val userModel = UserModel(1, "Raphael", "000", "000", 100.0)
+
+ @Mock
+ lateinit var mockedStatementsService: StatementsService
+
+ // Ao chamar fetchStatements com sucesso deve-se chamar presentStatements
+ @Test
+ fun onFetchStatementsSuccess_shouldCallPresentStatements() {
+ `when`(mockedStatementsService.getHttpClient())
+ .thenReturn(FakeSuccessHttpClient())
+ val interactor = StatementsInteractor()
+ interactor.statementsService = mockedStatementsService
+
+ val statementsInteractorOutputSpy = StatementsInteractorOutputSpy()
+ interactor.output = statementsInteractorOutputSpy
+
+ interactor.fetchStatements(1)
+
+ Assert.assertTrue(statementsInteractorOutputSpy.presentStatementsIsCalled)
+ }
+
+ // Ao chamar fetchStatements com erro deve-se chamar presentErrorMessage
+ @Test
+ fun onFetchStatementsFailure_shouldCallPresentErrorMessage() {
+ `when`(mockedStatementsService.getHttpClient())
+ .thenReturn(FakeFailureHttpClient())
+ val interactor = StatementsInteractor()
+ interactor.statementsService = mockedStatementsService
+
+ val statementsInteractorOutputSpy = StatementsInteractorOutputSpy()
+ interactor.output = statementsInteractorOutputSpy
+
+ interactor.fetchStatements(1)
+
+ Assert.assertTrue(statementsInteractorOutputSpy.presentErrorMessageIsCalled)
+ }
+
+ class StatementsInteractorOutputSpy: StatementsPresenterInput {
+ var presentUserIsCalled = false
+ var userCopy: UserModel? = null
+
+ var presentStatementsIsCalled = false
+
+ var presentErrorMessageIsCalled = false
+
+ override fun presentStatements(statements: ArrayList) {
+ presentStatementsIsCalled = true
+ }
+
+ override fun presentErrorMessage(title: String, msg: String) {
+ presentErrorMessageIsCalled = true
+ }
+
+ }
+
+ open class FakeSuccessHttpClient: HttpInterface {
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(Exception("Nenhum método post para lançamentos"))
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onSuccess(
+ JSONObject("""
+ {
+ "statementList": [
+ {
+ "title": "Pagamento",
+ "desc": "Conta de luz",
+ "date": "2018-08-15",
+ "value": -50
+ },
+ {
+ "title": "TED Recebida",
+ "desc": "Joao Alfredo",
+ "date": "2018-07-25",
+ "value": 745.03
+ }
+ ]
+ }
+ """)
+ )
+ }
+ }
+
+ open class FakeFailureHttpClient: HttpInterface {
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(Exception("Nenhum método post para lançamentos"))
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(java.lang.Exception("Erro ao buscar lançamentos"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsPresenterTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsPresenterTest.kt
new file mode 100644
index 000000000..257d747ff
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsPresenterTest.kt
@@ -0,0 +1,63 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import com.qintess.santanderapp.helper.Validator
+import com.qintess.santanderapp.model.StatementModel
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsActivityInput
+import com.qintess.santanderapp.ui.view.statementsScreen.StatementsPresenter
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import java.lang.ref.WeakReference
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class StatementsPresenterTest {
+ // presentStatements deve chamar updateList
+ @Test
+ fun onPresentStatements_shouldCallUpdateList() {
+ val presenter = StatementsPresenter()
+ val statementsPresenterOutputSpy = StatementsPresenterOutputSpy()
+ presenter.output = WeakReference(statementsPresenterOutputSpy)
+
+ val statements = ArrayList()
+ presenter.presentStatements(statements)
+
+ Assert.assertTrue(statementsPresenterOutputSpy.updateListIsCalled)
+ }
+
+ // presentErrorMsg deve chamar showAlert
+ @Test
+ fun onPresentErrorMsg_shouldCallShowAlert() {
+ val presenter = StatementsPresenter()
+ val statementsPresenterOutputSpy = StatementsPresenterOutputSpy()
+ presenter.output = WeakReference(statementsPresenterOutputSpy)
+
+ presenter.presentErrorMessage(Validator.STATEMENTS_TITLE_ERROR, "")
+
+ Assert.assertTrue(statementsPresenterOutputSpy.showAlertIsCalled)
+ }
+
+ class StatementsPresenterOutputSpy: StatementsActivityInput {
+ var updateListIsCalled = false
+ var showAlertIsCalled = false
+
+
+ override fun displayStatements(userId: Int) { return }
+
+ override fun updateList(statements: ArrayList) {
+ updateListIsCalled = true
+ }
+
+ override fun showAlert(title: String, msg: String): Boolean {
+ showAlertIsCalled = true
+ return true
+ }
+
+ override fun createListeners() { return }
+
+ override fun logout() { return }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsServiceTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsServiceTest.kt
new file mode 100644
index 000000000..7ee5c4945
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/StatementsServiceTest.kt
@@ -0,0 +1,98 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import com.qintess.santanderapp.model.StatementModel
+import com.qintess.santanderapp.service.*
+import org.json.JSONObject
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.runners.MockitoJUnitRunner
+import org.robolectric.annotation.Config
+
+@RunWith(MockitoJUnitRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class StatementsServiceTest {
+ @Mock
+ private lateinit var statementsService: StatementsService
+
+ // Um dos callbacks é chamado
+ @Test
+ fun getStatementsCallback_isCalled() {
+ // Esse teste só funciona pq o FakeHttpClient sobrescreve a funcionalidade da requisição e executa o código sincronamente. No uso real esse código é executado assincronamente
+ var callbackIsCalled = false
+ val successCallback: SuccessCallback> = {
+ callbackIsCalled = true
+ }
+ val failureCallback: FailureCallback = {
+ callbackIsCalled = true
+ }
+ `when`(statementsService.getHttpClient())
+ .thenReturn(FakeHttpClient())
+
+ statementsService.getStatements(1, successCallback, failureCallback)
+
+ Assert.assertTrue(callbackIsCalled)
+ }
+
+ // getStatements deve retornar lista de StatementModel
+ @Test
+ fun getStatements_shouldReturnNotNullStatementModelArrayList() {
+ val successCallback: SuccessCallback> = {
+ Assert.assertNotNull(it)
+ }
+ val failureCallback: FailureCallback = {
+ throw it
+ }
+ `when`(statementsService.getHttpClient())
+ .thenReturn(FakeHttpClient())
+
+ statementsService.getStatements(1, successCallback, failureCallback)
+ }
+
+ // getStatements deve retornar lista com número correto de items
+ @Test
+ fun getStatements_shouldReturnCorretSizeStatementModelArrayList() {
+ val successCallback: SuccessCallback> = {
+ Assert.assertEquals(2, it.size)
+ }
+ val failureCallback: FailureCallback = {
+ throw it
+ }
+ `when`(statementsService.getHttpClient())
+ .thenReturn(FakeHttpClient())
+
+ statementsService.getStatements(1, successCallback, failureCallback)
+ }
+
+ open class FakeHttpClient: HttpInterface {
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(Exception("Nenhum método post para lançamentos"))
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onSuccess(
+ JSONObject("""
+ {
+ "statementList": [
+ {
+ "title": "Pagamento",
+ "desc": "Conta de luz",
+ "date": "2018-08-15",
+ "value": -50
+ },
+ {
+ "title": "TED Recebida",
+ "desc": "Joao Alfredo",
+ "date": "2018-07-25",
+ "value": 745.03
+ }
+ ]
+ }
+ """)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/UserServiceTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/UserServiceTest.kt
new file mode 100644
index 000000000..14502898b
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/UserServiceTest.kt
@@ -0,0 +1,57 @@
+package com.qintess.santanderapp
+
+import android.os.Build
+import com.qintess.santanderapp.model.CredentialsModel
+import com.qintess.santanderapp.model.UserModel
+import com.qintess.santanderapp.service.*
+import org.json.JSONObject
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.runners.MockitoJUnitRunner
+import org.robolectric.annotation.Config
+
+@RunWith(MockitoJUnitRunner::class)
+@Config(sdk = [Build.VERSION_CODES.KITKAT])
+class UserServiceTest {
+ @Mock
+ private lateinit var userService: UserService
+
+ @Test
+ fun onRequestLogin_shouldPopulateAModel() {
+ val credentialsModel = CredentialsModel("raphael@email.com", "Santander@1")
+ val successCallback: SuccessCallback = {
+ Assert.assertNotNull(it)
+ }
+ val failureCallback: FailureCallback = {
+ throw it
+ }
+ `when`(userService.getHttpClient())
+ .thenReturn(FakeHttpClient())
+
+ userService.login(credentialsModel, successCallback, failureCallback)
+ }
+
+ open class FakeHttpClient: HttpInterface {
+ override fun post(url: String, bodyParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onSuccess(JSONObject("""
+ {
+ "userAccount": {
+ "userId": 1,
+ "name": "Jose da Silva Teste",
+ "bankAccount": "2050",
+ "agency": "012314564",
+ "balance": 3.3445
+ },
+ "error": {}
+ }
+ """))
+ }
+
+ override fun get(url: String, urlParameters: RequestParameters, onSuccess: SuccessCallback, onFailure: FailureCallback) {
+ onFailure(Exception("Nenhum método get para usuários"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/app/src/test/java/com/qintess/santanderapp/ValidatorTest.kt b/SantanderApp/app/src/test/java/com/qintess/santanderapp/ValidatorTest.kt
new file mode 100644
index 000000000..c380be4b8
--- /dev/null
+++ b/SantanderApp/app/src/test/java/com/qintess/santanderapp/ValidatorTest.kt
@@ -0,0 +1,49 @@
+package com.qintess.santanderapp
+
+import com.qintess.santanderapp.helper.Validator
+import org.junit.Assert
+import org.junit.Test
+
+class ValidatorTest {
+ // Validação de e-mail é válida
+ @Test
+ fun email_validation_isValid() {
+ // E-mail válido
+ Assert.assertTrue(Validator.isEmailValid("raphael@email.com"))
+
+ // E-mails inválidos
+ Assert.assertFalse(Validator.isEmailValid("raphael@email"))
+ Assert.assertFalse(Validator.isEmailValid("raphael.com"))
+ Assert.assertFalse(Validator.isEmailValid("**@**.com"))
+ Assert.assertFalse(Validator.isEmailValid(""))
+ }
+
+ // Validação de CPF é válida
+ @Test
+ fun cpf_validation_isValid() {
+ // CPF válido
+ Assert.assertTrue(Validator.isCpfValid("373.213.858-50"))
+ Assert.assertTrue(Validator.isCpfValid("37321385850"))
+
+ // CPFs inválidos
+ Assert.assertFalse(Validator.isCpfValid("373A21385850"))
+ Assert.assertFalse(Validator.isCpfValid("373.213.858-50SANTANDER"))
+ Assert.assertFalse(Validator.isCpfValid("000.000.000-00"))
+ Assert.assertFalse(Validator.isCpfValid("1234"))
+ Assert.assertFalse(Validator.isCpfValid("ABC"))
+ Assert.assertFalse(Validator.isCpfValid(""))
+ }
+
+ // Validação de senha é válida
+ @Test
+ fun pass_validation_isValid() {
+ // Senha válida
+ Assert.assertTrue(Validator.isPasswordValid("AppSantander123@"))
+
+ //Senhas inválidas
+ Assert.assertFalse(Validator.isPasswordValid("appsantander123@"))
+ Assert.assertFalse(Validator.isPasswordValid("AppSantander123"))
+ Assert.assertFalse(Validator.isPasswordValid("AppSantander@"))
+ Assert.assertFalse(Validator.isPasswordValid(""))
+ }
+}
\ No newline at end of file
diff --git a/SantanderApp/build.gradle b/SantanderApp/build.gradle
new file mode 100644
index 000000000..2eac75d74
--- /dev/null
+++ b/SantanderApp/build.gradle
@@ -0,0 +1,29 @@
+// 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"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/SantanderApp/gradle.properties b/SantanderApp/gradle.properties
new file mode 100644
index 000000000..beb2e675f
--- /dev/null
+++ b/SantanderApp/gradle.properties
@@ -0,0 +1,22 @@
+# 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=-Xmx1536m
+# 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
+android.enableUnitTestBinaryResources=false
diff --git a/SantanderApp/gradle/wrapper/gradle-wrapper.jar b/SantanderApp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f6b961fd5
Binary files /dev/null and b/SantanderApp/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/SantanderApp/gradle/wrapper/gradle-wrapper.properties b/SantanderApp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..2740ab420
--- /dev/null
+++ b/SantanderApp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Jun 09 20:49:06 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/SantanderApp/gradlew b/SantanderApp/gradlew
new file mode 100755
index 000000000..cccdd3d51
--- /dev/null
+++ b/SantanderApp/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/SantanderApp/gradlew.bat b/SantanderApp/gradlew.bat
new file mode 100644
index 000000000..f9553162f
--- /dev/null
+++ b/SantanderApp/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/SantanderApp/settings.gradle b/SantanderApp/settings.gradle
new file mode 100644
index 000000000..ccc3a8c61
--- /dev/null
+++ b/SantanderApp/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name='SantanderApp'
+include ':app'