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 @@ + + + + + + + + + + + + + +