diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..603b14077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..88ea3aa1e --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,122 @@ + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..42d66cb8a --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 000000000..a5f05cd8c --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..af0bbdde1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 000000000..7f68460d8 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Bank_App.postman_collection.json b/Bank_App.postman_collection.json deleted file mode 100644 index 53882a1c6..000000000 --- a/Bank_App.postman_collection.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "info": { - "_postman_id": "b90c97e1-4261-4a34-a348-a0604f0264a7", - "name": "Bank App", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/x-www-form-urlencoded", - "type": "text" - } - ], - "body": { - "mode": "urlencoded", - "urlencoded": [ - { - "key": "user", - "value": "test_user", - "type": "text" - }, - { - "key": "password", - "value": "Test@1", - "type": "text" - } - ] - }, - "url": { - "raw": "https://bank-app-test.herokuapp.com/api/login", - "protocol": "https", - "host": [ - "bank-app-test", - "herokuapp", - "com" - ], - "path": [ - "api", - "login" - ] - } - }, - "response": [] - }, - { - "name": "Statements", - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/x-www-form-urlencoded", - "type": "text" - } - ], - "body": { - "mode": "urlencoded", - "urlencoded": [ - { - "key": "user", - "value": "4", - "type": "text" - }, - { - "key": "password", - "value": "asdfa", - "type": "text" - } - ] - }, - "url": { - "raw": "https://bank-app-test.herokuapp.com/api/statements/1", - "protocol": "https", - "host": [ - "bank-app-test", - "herokuapp", - "com" - ], - "path": [ - "api", - "statements", - "1" - ] - } - }, - "response": [] - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index bd73feb5f..74e92ff87 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,3 @@ -# Show me the code +# Desafio Android - Luis Claudio Monteoliva -Esse repositório contem todo o material necessário para realizar o teste: -- A especificação do layout está na pasta 'bank_app_layout' abrindo o index.html, utilizar os Styles do Android - -- Os dados da Api estão mockados, os exemplos e a especificação dos serviços (login e statements) se encontram no arquivo BankApp.postman_collection.json ( é necessário instalar o postman e importar a colection https://www.getpostman.com/apps) - -![Image of Yaktocat](https://github.com/SantanderTecnologia/TesteiOS/blob/new_test/telas.png) - -### # DESAFIO: - -Na primeira tela teremos um formulario de login, o campo user deve aceitar email ou cpf, -o campo password deve validar se a senha tem pelo menos uma letra maiuscula, um caracter especial e um caracter alfanumérico. -Apos a validação, realizar o login no endpoint https://bank-app-test.herokuapp.com/api/login e exibir os dados de retorno na próxima tela. -O ultimo usuário logado deve ser salvo de forma segura localmente, e exibido na tela de login se houver algum salvo. - -Na segunda tela será exibido os dados formatados do retorno do login e será necessário fazer um segundo request para obter os lançamentos do usuário, no endpoint https://bank-app-test.herokuapp.com/api/statements/{idUser} que retornará uma lista de lançamentos - -### # Avaliação - -Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura do app. É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits. - -Obrigatórios: - -* Java ou Kotlin -* Material Design -* O app deve funcionar a partir do android 4.4 -* Testes unitários, pode usar a ferramenta que você tem mais experiência, só nos explique o que ele tem de bom. -* Arquitetura a ser utilizada: Android Clean Code (https://github.com/kmmraj/android-clean-code && https://medium.com/@kmmraj/android-clean-code-part-1-c66da6551d1) -* Uso do git. - -### # Observações gerais - -Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto. -Pedimos que trabalhe sozinho e não divulgue o resultado na internet. - -Faça um fork desse desse repositório em seu Github e ao finalizar nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando. - -# Importante: não há prazo de entrega, faça com qualidade! - -# BOA SORTE! +Não ha necessidades de instalação de qualquer biblioteca, foi utilizado gestão de independência de bibliotecas diretamente no Gradle \ No newline at end of file diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 2f7efbeab..000000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-minimal \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..51377c6f7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,50 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + + defaultConfig { + applicationId "br.com.testeandroidv2" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + debuggable true + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + testImplementation 'junit:junit:4.12' + + implementation 'com.google.android.material:material:1.1.0' + + // AndroidX + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + // AnroidX test + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + // GSON + implementation 'com.google.code.gson:gson:2.8.6' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/br/com/testeandroidv2/ExampleInstrumentedTest.kt b/app/src/androidTest/java/br/com/testeandroidv2/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..617b9efd3 --- /dev/null +++ b/app/src/androidTest/java/br/com/testeandroidv2/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package br.com.testeandroidv2 + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("br.com.testeandroidv2", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..65406086e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/bean/LoginBean.kt b/app/src/main/java/br/com/testeandroidv2/model/bean/LoginBean.kt new file mode 100644 index 000000000..df2a60ef9 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/bean/LoginBean.kt @@ -0,0 +1,12 @@ +package br.com.testeandroidv2.model.bean + +import android.os.Parcelable + +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class LoginBean(val userId: Int, + val name: String, + val bankAccount: String, + val agency: String, + val balance: Double) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/enums/Endpoints.kt b/app/src/main/java/br/com/testeandroidv2/model/enums/Endpoints.kt new file mode 100644 index 000000000..d1eb1fe1b --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/enums/Endpoints.kt @@ -0,0 +1,8 @@ +package br.com.testeandroidv2.model.enums + +enum class Endpoints(private val endpoint: String) { + LOGIN("/login"), + STATEMENTS("/statements/{0}"); + + fun endpoint(): String = endpoint +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/login/Model.kt b/app/src/main/java/br/com/testeandroidv2/model/login/Model.kt new file mode 100644 index 000000000..a25fb926f --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/login/Model.kt @@ -0,0 +1,54 @@ +package br.com.testeandroidv2.model.login + +import org.json.JSONObject +import org.json.JSONException + +import br.com.testeandroidv2.model.bean.LoginBean +import br.com.testeandroidv2.model.enums.Endpoints +import br.com.testeandroidv2.presenter.login.MVP +import br.com.testeandroidv2.utils.Constantes +import br.com.testeandroidv2.utils.http.HttpAction +import br.com.testeandroidv2.utils.http.HttpCallBack +import br.com.testeandroidv2.utils.http.HttpResponse +import br.com.testeandroidv2.utils.http.HttpStatus + +class Model(private val presenter: MVP.Presenter): MVP.Model { + + override fun loadLogin(user: String, password: String) { + presenter.showProgressBar(true) + + val wsEndpoint: String = Endpoints.LOGIN.endpoint() + val baseUrl: String = Constantes.baseURL + wsEndpoint + val jsonBody = "user=$user&password=$password" + + val action = HttpAction(presenter.context) + action.addHeader("Content-Type", "application/x-www-form-urlencoded") + action.addHeader("Cache-Control","no-cache") + action.send(HttpStatus.POST, + baseUrl, + jsonBody, + object : HttpCallBack { + override fun onResponse(response: HttpResponse?) { + try { + val json = JSONObject(response?.message) + val account = JSONObject(json.getString("userAccount")) + val loginBean = LoginBean( + account.getInt("userId"), + account.getString("name"), + account.getString("bankAccount"), + account.getString("agency"), + account.getDouble("balance") + ) + + presenter.updateData(loginBean) + presenter.showProgressBar(false) + } + catch (je: JSONException) {} + } + + override fun onError(response: HttpResponse?) { + presenter.showProgressBar(false) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/statements/Model.kt b/app/src/main/java/br/com/testeandroidv2/model/statements/Model.kt new file mode 100644 index 000000000..dc3790209 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/statements/Model.kt @@ -0,0 +1,42 @@ +package br.com.testeandroidv2.model.statements + +import java.text.MessageFormat + +import com.google.gson.Gson + +import br.com.testeandroidv2.model.enums.Endpoints +import br.com.testeandroidv2.model.statements.gson.Statement +import br.com.testeandroidv2.presenter.statements.MVP +import br.com.testeandroidv2.utils.Constantes +import br.com.testeandroidv2.utils.http.HttpAction +import br.com.testeandroidv2.utils.http.HttpCallBack +import br.com.testeandroidv2.utils.http.HttpResponse +import br.com.testeandroidv2.utils.http.HttpStatus + +class Model(private val presenter: MVP.Presenter): MVP.Model { + + override fun loadList(userId: Int) { + presenter.showProgressBar(true) + + val wsEndpoint: String = Endpoints.STATEMENTS.endpoint() + val baseUrl: String = Constantes.baseURL + MessageFormat.format(wsEndpoint, userId.toString()) + + val action = HttpAction(presenter.context) + action.send( + HttpStatus.GET, + baseUrl, + null, + object : HttpCallBack { + override fun onResponse(response: HttpResponse?) { + val result: Statement = Gson().fromJson(response?.message, Statement::class.java) + + presenter.updateListRecycler(result.statementList) + presenter.showProgressBar(false) + } + + override fun onError(response: HttpResponse?) { + presenter.showProgressBar(false) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/statements/gson/Error.kt b/app/src/main/java/br/com/testeandroidv2/model/statements/gson/Error.kt new file mode 100644 index 000000000..80c53e899 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/statements/gson/Error.kt @@ -0,0 +1,7 @@ +package br.com.testeandroidv2.model.statements.gson + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Error(val code: Int? = null, var message: String? = null) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/statements/gson/Statement.kt b/app/src/main/java/br/com/testeandroidv2/model/statements/gson/Statement.kt new file mode 100644 index 000000000..a4a30d0c1 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/statements/gson/Statement.kt @@ -0,0 +1,13 @@ +package br.com.testeandroidv2.model.statements.gson + +import android.os.Parcelable + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Statement(@SerializedName("statementList") @Expose var statementList: MutableList? = null, + @SerializedName("error") @Expose var error: Error? = null +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/model/statements/gson/StatementList.kt b/app/src/main/java/br/com/testeandroidv2/model/statements/gson/StatementList.kt new file mode 100644 index 000000000..ce1183659 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/model/statements/gson/StatementList.kt @@ -0,0 +1,15 @@ +package br.com.testeandroidv2.model.statements.gson + +import android.os.Parcelable + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName + +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class StatementList(@SerializedName("title") @Expose var title: String? = null, + @SerializedName("desc") @Expose var desc: String? = null, + @SerializedName("date") @Expose var date: String? = null, + @SerializedName("value") @Expose var value: Double? = null +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/presenter/login/MVP.kt b/app/src/main/java/br/com/testeandroidv2/presenter/login/MVP.kt new file mode 100644 index 000000000..d6701f2c5 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/presenter/login/MVP.kt @@ -0,0 +1,24 @@ +package br.com.testeandroidv2.presenter.login + +import android.content.Context + +import br.com.testeandroidv2.model.bean.LoginBean + +class MVP { + interface Model { + fun loadLogin(user: String, password: String) + } + + interface Presenter { + val context: Context + fun setView(view: View) + fun showProgressBar(status: Boolean) + fun loadLogin(user: String, password: String) + fun updateData(loginBean: LoginBean) + } + + interface View { + fun showProgressBar(visible: Int) + fun updateData(loginBean: LoginBean) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/presenter/login/Presenter.kt b/app/src/main/java/br/com/testeandroidv2/presenter/login/Presenter.kt new file mode 100644 index 000000000..16eefe941 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/presenter/login/Presenter.kt @@ -0,0 +1,30 @@ +package br.com.testeandroidv2.presenter.login + +import android.content.Context +import android.view.View +import br.com.testeandroidv2.model.bean.LoginBean + +import br.com.testeandroidv2.model.login.Model + +class Presenter : MVP.Presenter { + private lateinit var view: MVP.View + + private val model: MVP.Model = Model(this) + + override val context: Context + get() = view as Context + + override fun setView(view: MVP.View) { this.view = view } + override fun showProgressBar(status: Boolean) { + val visible: Int = if (status) View.VISIBLE else View.GONE + view.showProgressBar(visible) + } + + override fun loadLogin(user: String, password: String) { + model.loadLogin(user, password) + } + + override fun updateData(loginBean: LoginBean) { + view.updateData(loginBean) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/presenter/statements/MVP.kt b/app/src/main/java/br/com/testeandroidv2/presenter/statements/MVP.kt new file mode 100644 index 000000000..876d47e17 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/presenter/statements/MVP.kt @@ -0,0 +1,24 @@ +package br.com.testeandroidv2.presenter.statements + +import android.content.Context +import br.com.testeandroidv2.model.statements.gson.StatementList + +class MVP { + interface Model { + fun loadList(userId: Int) + } + + interface Presenter { + val context: Context + val items: MutableList + fun setView(view: View) + fun showProgressBar(status: Boolean) + fun loadList(userId: Int) + fun updateListRecycler(list: MutableList?) + } + + interface View { + fun showProgressBar(visible: Int) + fun updateListRecycler() + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/presenter/statements/Presenter.kt b/app/src/main/java/br/com/testeandroidv2/presenter/statements/Presenter.kt new file mode 100644 index 000000000..816485fbb --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/presenter/statements/Presenter.kt @@ -0,0 +1,35 @@ +package br.com.testeandroidv2.presenter.statements + +import android.content.Context +import android.view.View + +import br.com.testeandroidv2.model.statements.Model +import br.com.testeandroidv2.model.statements.gson.StatementList + +class Presenter : MVP.Presenter { + private lateinit var view: MVP.View + + private val model: Model = Model(this) + private var list: MutableList = emptyList().toMutableList() + + override val context: Context + get() = view as Context + override val items: MutableList + get() = list + + override fun setView(view: MVP.View) { this.view = view } + override fun showProgressBar(status: Boolean) { + val visible: Int = if (status) View.VISIBLE else View.GONE + view.showProgressBar(visible) + } + + override fun loadList(userId: Int) { + model.loadList(userId) + } + + override fun updateListRecycler(list: MutableList?) { + this.list.clear() + this.list.addAll(list!!) + view.updateListRecycler() + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/Constantes.kt b/app/src/main/java/br/com/testeandroidv2/utils/Constantes.kt new file mode 100644 index 000000000..26de9fb74 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/Constantes.kt @@ -0,0 +1,7 @@ +package br.com.testeandroidv2.utils + +interface Constantes { + companion object { + const val baseURL: String = "https://bank-app-test.herokuapp.com/api" + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/Utils.kt b/app/src/main/java/br/com/testeandroidv2/utils/Utils.kt new file mode 100644 index 000000000..b46833464 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/Utils.kt @@ -0,0 +1,113 @@ +package br.com.testeandroidv2.utils + +import android.content.Context +import android.content.SharedPreferences +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import java.math.RoundingMode +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + + +object Utils { + fun setStringConfig(context: Context, key: String, value: String) { + var settings: SharedPreferences = context.getSharedPreferences("config", Context.MODE_PRIVATE) + var editor: SharedPreferences.Editor = settings.edit() + editor.putString(key, value) + editor.apply() + } + + fun getStringConfig(context: Context, key: String?) : String? { + var config: SharedPreferences = context.getSharedPreferences("config", Context.MODE_PRIVATE) + return config.getString(key, "") + } + + @Suppress("DEPRECATION") + fun isInternetConnect(context: Context): Boolean { + var result = false + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + cm?.run { + cm.getNetworkCapabilities(cm.activeNetwork)?.run { + result = when { + hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true + else -> false + } + } + } + } + else { + cm?.run { + cm.activeNetworkInfo?.run { + if (type == ConnectivityManager.TYPE_WIFI) { + result = true + } + else if (type == ConnectivityManager.TYPE_MOBILE) { + result = true + } + } + } + } + return result + } + + fun formatDecimal(number: Double?): String? { + val locale = Locale("pt", "BR") + val df = DecimalFormat("###,##0.00", DecimalFormatSymbols(locale)) + df.roundingMode = RoundingMode.FLOOR + return df.format(number) + } + + fun isValidPassword(password: String) : Boolean { + val caracter = ".*[A-Z].*".toRegex() + val number = ".*[0-9].*".toRegex() + val special = ".*[#&\$*%@!].*".toRegex() + + if (!caracter.matches(password)) { return false } + if (!number.matches(password)) { return false } + else if (!special.matches(password)) { return false } + + return true + } + + fun formatDate(date: String?): String? { + val f0 = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val f1 = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) + + try { + val c = GregorianCalendar(Locale.getDefault()) + c.time = f0.parse(date) + return f1.format(c.time) + } + catch (pe: ParseException) { + } + return "" + } + + fun getAgency(value: String): String? { + val total = value.length + var campo1 = "" + var campo2 = "" + var campo3 = "" + for (c in 0 until total) { + val character = value[c].toString() + if (c < 2) { + campo1 += character + } + else if (c < total - 1) { + campo2 += character + } + else { + campo3 += character + } + } + return "$campo1.$campo2-$campo3" + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/UtilsAnimation.kt b/app/src/main/java/br/com/testeandroidv2/utils/UtilsAnimation.kt new file mode 100644 index 000000000..964a69b62 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/UtilsAnimation.kt @@ -0,0 +1,21 @@ +package br.com.testeandroidv2.utils + +import androidx.appcompat.app.AppCompatActivity + +import br.com.testeandroidv2.R + +object UtilsAnimation { + /** + * Method to animate activity Left to Right + */ + fun leftToRight(activity: AppCompatActivity) { + activity.overridePendingTransition(R.anim.lefttoright, R.anim.stable) + } + + /** + * Method to animate activity Right to Left + */ + fun rightToLeft(activity: AppCompatActivity) { + activity.overridePendingTransition(R.anim.righttoleft, R.anim.stable) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/Http.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/Http.kt new file mode 100644 index 000000000..b9c0b5052 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/Http.kt @@ -0,0 +1,171 @@ +package br.com.testeandroidv2.utils.http + +import java.io.* +import java.net.HttpURLConnection +import java.net.MalformedURLException +import java.net.SocketTimeoutException +import java.net.URL + +class Http(private val requestParams: MutableList?, + private val headerParams: MutableList?) { + + companion object { + private const val DEFAULT_CHARSET = "UTF-8" + private const val SIXTY_SECONDS = 60000 + } + + fun sendGet(sendUrl: String?): HttpResponse? { + return send(sendUrl, null, HttpStatus.GET) + } + + fun sendPost(sendUrl: String?, jsonBody: String?): HttpResponse? { + return send(sendUrl, jsonBody, HttpStatus.POST) + } + + private fun send(sendUrl: String?, jsonBody: String?, method: Int?): HttpResponse? { + var result: HttpResponse? = null + var urlToRead: String? = sendUrl + + val rd: BufferedReader + val start: Long = System.currentTimeMillis() + var connResponseMessage = "" + try { + // inicia + var line: String? + var urlParameters = "" + var sepa = if (method == HttpStatus.GET) { "?" } else { "" } + var requestMethod: String = when (method) { + HttpStatus.GET -> "GET" + HttpStatus.POST -> "POST" + HttpStatus.PUT -> "PUT" + HttpStatus.DELETE -> "DELETE" + else -> "" + } + + if (requestMethod.isEmpty()) { return errorResponse() } + + // verifica os params + requestParams?.forEach { + val linha = "${it.nome}=${it.valor}" + urlParameters += if (urlParameters.isNotEmpty()) { "&$linha" } else { "$sepa$linha" } + } + + // incrementa a URL + if (urlParameters.isNotEmpty() && method == HttpStatus.GET) { urlToRead += urlParameters } + + // realiza a conexao + val conn = URL(urlToRead).openConnection() as HttpURLConnection + conn.requestMethod = requestMethod + conn.doInput = true + conn.useCaches = false + conn.readTimeout = SIXTY_SECONDS + conn.connectTimeout = SIXTY_SECONDS + + if (method == HttpStatus.POST) { conn.doOutput = true } + + // verifica os headers + headerParams?.forEach { conn.setRequestProperty(it.nome, it.valor) } + + if (method == HttpStatus.POST) { + // send data + val wr = DataOutputStream(conn.outputStream) + + // parametrs + if (urlParameters.isNotEmpty()) { + wr.write(urlParameters.toByteArray(charset(DEFAULT_CHARSET))) + } + + // JSON Object + if (jsonBody != null) { + wr.write(jsonBody.toByteArray(charset(DEFAULT_CHARSET))) + } + + // flush send data + wr.flush() + wr.close() + } + + // initialize InputStream + val input: InputStream + + // status response + val codeStatus = conn.responseCode + connResponseMessage = conn.responseMessage + + // verify status + input = if (codeStatus == HttpURLConnection.HTTP_OK || + codeStatus == HttpURLConnection.HTTP_CREATED) { + conn.inputStream + } + else { conn.errorStream } + + // seta o BufferReader + rd = BufferedReader(InputStreamReader(input, DEFAULT_CHARSET)) + + // message + var message: String? = "" + + // percorre o arquivo e retorna o result + while (rd.readLine().also { line = it } != null) { + message += line + } + + // set Result + result = HttpResponse() + result.codeHttp = codeStatus + result.codeError = 0 + result.message = message + result.status = "OK" + + // fecha o BufferedReader + rd.close() + conn.disconnect() + } + catch (me: MalformedURLException) { + connResponseMessage = "MalformedURLException: " + me.message + } + catch (ue: UnsupportedEncodingException) { + connResponseMessage = "UnsupportedEncodingException: " + ue.message + } + catch (ie: IOException) { + if (ie is SocketTimeoutException) { + result = HttpResponse() + result.codeHttp = HttpURLConnection.HTTP_CLIENT_TIMEOUT + result.codeError = 99 + result.message = "" + result.status = "ERROR" + } + else { + connResponseMessage = "IOException: " + ie.message + } + } + + if (result == null) { + result = HttpResponse() + result.codeHttp = HttpURLConnection.HTTP_BAD_REQUEST + result.codeError = 99 + result.message = "" + result.status = "ERROR" + } + + // calcula o tempo em milesegundos + val finish: Long = System.currentTimeMillis() + val total: Long = finish - start + + // set Response + result.timeFinish = total + result.messageResponse = connResponseMessage + + // retorna + return result + } + + private fun errorResponse(): HttpResponse { + val response = HttpResponse() + response.codeHttp = HttpURLConnection.HTTP_BAD_REQUEST + response.codeError = 99 + response.message = "Method invalid" + response.status = "Error" + return response + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/HttpAction.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpAction.kt new file mode 100644 index 000000000..9a004d6a4 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpAction.kt @@ -0,0 +1,43 @@ +package br.com.testeandroidv2.utils.http + +import java.net.HttpURLConnection +import android.content.Context +import org.json.JSONObject + +import br.com.testeandroidv2.utils.Utils + +class HttpAction(private val context: Context) { + private val requestParams: MutableList = emptyList().toMutableList() + private val headerParams: MutableList = emptyList().toMutableList() + + fun add(name: String?, value: String?) { requestParams.add(HttpParams(name, value)) } + fun addHeader(name: String?, value: String?) { headerParams.add(HttpParams(name, value)) } + + fun send(method: Int, urlSend: String, jsonBody: String?, callBack: HttpCallBack) { + if (!Utils.isInternetConnect(context)) { + callBack.onResponse(errorResponse()) + return + } + + HttpTask( + context, + method, + headerParams, + requestParams, + jsonBody, + object : HttpCallBack { + override fun onResponse(response: HttpResponse?) { callBack.onResponse(response) } + override fun onError(response: HttpResponse?) { callBack.onError(response) } + } + ).execute(urlSend) + } + + private fun errorResponse(): HttpResponse { + val response = HttpResponse() + response.codeHttp = HttpURLConnection.HTTP_BAD_REQUEST + response.codeError = 99 + response.message = "Internet Error" + response.status = "Error" + return response + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/HttpCallBack.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpCallBack.kt new file mode 100644 index 000000000..e7a5fe222 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpCallBack.kt @@ -0,0 +1,6 @@ +package br.com.testeandroidv2.utils.http + +interface HttpCallBack { + fun onResponse(response: HttpResponse?) + fun onError(response: HttpResponse?) +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/HttpParams.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpParams.kt new file mode 100644 index 000000000..11e21f621 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpParams.kt @@ -0,0 +1,7 @@ +package br.com.testeandroidv2.utils.http + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class HttpParams(var nome: String? = null, var valor: String? = null) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/HttpResponse.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpResponse.kt new file mode 100644 index 000000000..aa6f76b49 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpResponse.kt @@ -0,0 +1,13 @@ +package br.com.testeandroidv2.utils.http + +import android.os.Parcelable + +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class HttpResponse(var codeHttp: Int = 0, + var codeError: Int = 0, + var message: String? = null, + var status: String? = null, + var timeFinish: Long = 0, + var messageResponse: String? = null) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/HttpStatus.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpStatus.kt new file mode 100644 index 000000000..00b9afdf7 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpStatus.kt @@ -0,0 +1,10 @@ +package br.com.testeandroidv2.utils.http + +interface HttpStatus { + companion object { + const val GET = 1 + const val POST = 2 + const val PUT = 3 + const val DELETE = 4 + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/utils/http/HttpTask.kt b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpTask.kt new file mode 100644 index 000000000..562e05a53 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/utils/http/HttpTask.kt @@ -0,0 +1,53 @@ +package br.com.testeandroidv2.utils.http + +import java.net.HttpURLConnection +import android.content.Context +import android.os.AsyncTask + +import br.com.testeandroidv2.R + +class HttpTask(private val context: Context, + private val method: Int, + private val headerParams: MutableList?, + private val requestParams: MutableList?, + private val jsonBody: String?, + private val callBack: HttpCallBack?) : AsyncTask() { + + override fun doInBackground(vararg params: String?): HttpResponse? { + val sendUrl: String? = params[0] + val http = Http(requestParams, headerParams) + return when (method) { + HttpStatus.GET -> http.sendGet(sendUrl) + HttpStatus.POST -> http.sendPost(sendUrl, jsonBody) + HttpStatus.PUT -> null + HttpStatus.DELETE -> null + else -> null + } + } + + override fun onPostExecute(response: HttpResponse?) { + if (callBack != null && response != null) { + val codeHttp = response.codeHttp + + if (codeHttp == HttpURLConnection.HTTP_OK || + codeHttp == HttpURLConnection.HTTP_CREATED) { + if (response.status == "ERROR") { + callBack.onError(response) + } + else { + callBack.onResponse(response) + } + } + else { + if (codeHttp == HttpURLConnection.HTTP_CLIENT_TIMEOUT) { + response.message = context.getString(R.string.conection_error) + } + + callBack.onError(response) + } + } + else { + callBack!!.onError(response) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/view/DefaultActivity.kt b/app/src/main/java/br/com/testeandroidv2/view/DefaultActivity.kt new file mode 100644 index 000000000..53c0ffa9c --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/view/DefaultActivity.kt @@ -0,0 +1,62 @@ +package br.com.testeandroidv2.view + +import android.app.Activity +import android.content.DialogInterface +import android.view.KeyEvent + +import androidx.appcompat.app.ActionBar +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar + +import br.com.testeandroidv2.R +import br.com.testeandroidv2.utils.UtilsAnimation +import br.com.testeandroidv2.view.listener.OnCallback + +abstract class DefaultActivity : AppCompatActivity() { + private var actionBar: ActionBar? = null + private var mToolbar: Toolbar? = null + + fun setupToolBar(resource: Int) { + mToolbar = findViewById(resource) + setSupportActionBar(mToolbar) + actionBar = supportActionBar + } + + fun setActionBarHome() { actionBar?.setHomeButtonEnabled(true) } + fun setActionBarHomeButton() { actionBar?.setDisplayHomeAsUpEnabled(true) } + + fun setActionBarNotHome() { actionBar?.setHomeButtonEnabled(false) } + fun setActionBarNotHomeButton() { actionBar?.setDisplayHomeAsUpEnabled(false) } + + fun setActionBarTitle(title: String) { actionBar?.title = title } + fun setActionBarTitle(title: Int) { actionBar?.title = getString(title) } + + fun setActionBarSubTitle(title: String) { actionBar?.subtitle = title } + fun setActionBarSubTitle(title: Int) { actionBar?.subtitle = getString(title) } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + return if (keyCode == KeyEvent.KEYCODE_BACK) { + back(Activity.RESULT_CANCELED) + true + } + else { + super.onKeyDown(keyCode, event) + } + } + + fun animLeftToRight() { UtilsAnimation.leftToRight(this) } + fun animRightToLeft() { UtilsAnimation.rightToLeft(this) } + + fun msgBox(msg: String, callback: OnCallback) { + val dialog = AlertDialog.Builder(this, R.style.AlertDialogTheme) + dialog.setMessage(msg) + dialog.setCancelable(false) + dialog.setPositiveButton("OK") { dialog1: DialogInterface?, whichButton: Int -> + callback.onSuccess() + } + dialog.create().show() + } + + abstract fun back(resultCode: Int) +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/view/LoginActivity.kt b/app/src/main/java/br/com/testeandroidv2/view/LoginActivity.kt new file mode 100644 index 000000000..967b0a77b --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/view/LoginActivity.kt @@ -0,0 +1,117 @@ +package br.com.testeandroidv2.view + +import android.content.Intent +import android.os.Bundle +import android.text.method.PasswordTransformationMethod +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.Button + +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout + +import br.com.testeandroidv2.R +import br.com.testeandroidv2.model.bean.LoginBean +import br.com.testeandroidv2.presenter.login.MVP +import br.com.testeandroidv2.presenter.login.Presenter +import br.com.testeandroidv2.utils.Utils +import br.com.testeandroidv2.view.components.Progress +import br.com.testeandroidv2.view.listener.OnCallback + +class LoginActivity : DefaultActivity(), MVP.View { + private lateinit var progress: Progress + private lateinit var btnLoginEnter: Button + private lateinit var userLogin: TextInputEditText + private lateinit var passLogin: TextInputEditText + private lateinit var presenter: MVP.Presenter + private lateinit var passLoginInput: TextInputLayout + private lateinit var userLoginInput: TextInputLayout + + private var enableText: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_login) + + progress = findViewById(R.id.progressLogin) + progress.hide() + + userLoginInput = findViewById(R.id.userLoginInput) + passLoginInput = findViewById(R.id.passLoginInput) + userLogin = findViewById(R.id.userLogin) + passLogin = findViewById(R.id.passLogin) + passLogin.setOnEditorActionListener{ textView, actionId, keyEvent -> + when(actionId) { + EditorInfo.IME_ACTION_DONE -> { + enter() + false + } + else -> false + } + } + + btnLoginEnter = findViewById(R.id.btnLoginEnter) + btnLoginEnter.setOnClickListener { enter() } + + presenter = Presenter() + presenter.setView(this) + } + + private fun setupPasswordToggleView(field: TextInputEditText, enable: Boolean) { + passLogin.transformationMethod = when(enable) { + true -> null + false -> PasswordTransformationMethod() + else -> PasswordTransformationMethod() + } + } + + private fun enter() { + val user: String = userLogin.editableText.toString() + val password: String = passLogin.editableText.toString() + + if (user.isEmpty()) { + userLoginInput.error = getString(R.string.login_error1) + return + } + else if (user.isEmpty()) { + passLoginInput.error = getString(R.string.login_error2) + return + } + else if (!Utils.isValidPassword(password)) { + msgBox(getString(R.string.login_error3), object : OnCallback { + override fun onSuccess() {} + }) + return + } + + presenter.loadLogin(user, password) + } + + private fun statements(loginBean: LoginBean) { + val bundle = Bundle() + bundle.putParcelable("LOGIN_DATA", loginBean) + + val intent = Intent(this, StatementsActivity::class.java) + intent.putExtras(bundle) + + startActivity(intent) + finish() + animRightToLeft() + } + + override fun showProgressBar(visible: Int) { + when(visible) { + View.VISIBLE -> progress.show() + View.GONE -> progress.hide() + } + } + + override fun updateData(loginBean: LoginBean) { + msgBox(getString(R.string.login_ok), object : OnCallback { + override fun onSuccess() { statements(loginBean) } + }) + } + + override fun back(resultCode: Int) { finish() } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/view/StatementsActivity.kt b/app/src/main/java/br/com/testeandroidv2/view/StatementsActivity.kt new file mode 100644 index 000000000..3c0407ede --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/view/StatementsActivity.kt @@ -0,0 +1,106 @@ +package br.com.testeandroidv2.view + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.TextView + +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +import br.com.testeandroidv2.R +import br.com.testeandroidv2.model.bean.LoginBean +import br.com.testeandroidv2.presenter.statements.Presenter +import br.com.testeandroidv2.presenter.statements.MVP +import br.com.testeandroidv2.utils.Utils +import br.com.testeandroidv2.view.adapter.ItemAdapter +import br.com.testeandroidv2.view.components.Progress + +class StatementsActivity : DefaultActivity(), MVP.View { + private lateinit var progress: Progress + private lateinit var accountNumber: TextView + private lateinit var accountSaldo: TextView + private lateinit var presenter: MVP.Presenter + private lateinit var adapter: ItemAdapter + private lateinit var rv: RecyclerView + + private var loginBean: LoginBean? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_statements) + + val bundle: Bundle? = intent.extras + if (bundle != null) { + loginBean = bundle.getParcelable("LOGIN_DATA") + } + + setupToolBar(R.id.toolbar) + setActionBarTitle(loginBean!!.name) + setActionBarSubTitle("") + + progress = findViewById(R.id.progress) + progress.hide() + + accountNumber = findViewById(R.id.accountNumber) + accountSaldo = findViewById(R.id.accountSaldo) + + accountNumber.text = loginBean!!.bankAccount + " / " + Utils.getAgency(loginBean!!.agency) + accountSaldo.text = "R$" + Utils.formatDecimal(loginBean!!.balance) + + presenter = Presenter() + presenter.setView(this) + presenter.loadList(loginBean!!.userId) + + Handler().postDelayed({ onLoad() }, 100) + } + + private fun onLoad() { + val layoutManager = LinearLayoutManager(this) + layoutManager.orientation = LinearLayoutManager.VERTICAL + + adapter = ItemAdapter(presenter.items) + + rv = findViewById(R.id.rv) + rv.setHasFixedSize(true) + rv.layoutManager = layoutManager + rv.adapter = adapter + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when(item.itemId) { + R.id.action_logout -> { + back(Activity.RESULT_OK) + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun showProgressBar(visible: Int) { + when(visible) { + View.VISIBLE -> progress.show() + View.GONE -> progress.hide() + } + } + + override fun updateListRecycler() { + adapter.notifyDataSetChanged() + } + + override fun back(resultCode: Int) { + startActivity(Intent(this, LoginActivity::class.java)) + finish() + animLeftToRight() + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/view/adapter/ItemAdapter.kt b/app/src/main/java/br/com/testeandroidv2/view/adapter/ItemAdapter.kt new file mode 100644 index 000000000..36d84905f --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/view/adapter/ItemAdapter.kt @@ -0,0 +1,46 @@ +package br.com.testeandroidv2.view.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView + +import androidx.recyclerview.widget.RecyclerView + +import br.com.testeandroidv2.R +import br.com.testeandroidv2.model.statements.gson.StatementList +import br.com.testeandroidv2.utils.Utils + +class ItemAdapter(private val list: MutableList) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater + .from(parent.context) + .inflate(R.layout.item, parent, false) + return ViewHolder(view) + } + + override fun getItemCount(): Int = list.size + + override fun getItemId(position: Int): Long = position.toLong() + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item: StatementList = getItem(position) + + holder.apply { + itemColumn1.text = item.title + itemColumn2.text = item.desc + itemColumn3.text = Utils.formatDate(item.date) + itemColumn4.text = "R$" + Utils.formatDecimal(item.value) + } + } + + fun getItem(position: Int): StatementList = list[position] + + class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + val itemColumn1: TextView = itemView.findViewById(R.id.itemColumn1) + val itemColumn2: TextView = itemView.findViewById(R.id.itemColumn2) + val itemColumn3: TextView = itemView.findViewById(R.id.itemColumn3) + val itemColumn4: TextView = itemView.findViewById(R.id.itemColumn4) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/view/components/Progress.kt b/app/src/main/java/br/com/testeandroidv2/view/components/Progress.kt new file mode 100644 index 000000000..b211a512e --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/view/components/Progress.kt @@ -0,0 +1,32 @@ +package br.com.testeandroidv2.view.components + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import androidx.core.content.ContextCompat + +import br.com.testeandroidv2.R + +class Progress(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { + lateinit var view: View + + init { + start(context, attrs) + } + + private fun start(context: Context, attrs: AttributeSet) { + // seta o Background + setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent)) + + // pega o inflater + val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + + // pega a View + view = inflater.inflate(R.layout.progress, this) + } + + fun show() { view.visibility = View.VISIBLE } + fun hide() { view.visibility = View.GONE } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/testeandroidv2/view/listener/OnCallback.kt b/app/src/main/java/br/com/testeandroidv2/view/listener/OnCallback.kt new file mode 100644 index 000000000..90d801033 --- /dev/null +++ b/app/src/main/java/br/com/testeandroidv2/view/listener/OnCallback.kt @@ -0,0 +1,5 @@ +package br.com.testeandroidv2.view.listener + +interface OnCallback { + fun onSuccess() +} \ No newline at end of file diff --git a/app/src/main/res/anim/lefttoright.xml b/app/src/main/res/anim/lefttoright.xml new file mode 100644 index 000000000..d0b1d9f76 --- /dev/null +++ b/app/src/main/res/anim/lefttoright.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/righttoleft.xml b/app/src/main/res/anim/righttoleft.xml new file mode 100644 index 000000000..ca2976d5e --- /dev/null +++ b/app/src/main/res/anim/righttoleft.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/stable.xml b/app/src/main/res/anim/stable.xml new file mode 100644 index 000000000..bcbe4235a --- /dev/null +++ b/app/src/main/res/anim/stable.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bank.xml b/app/src/main/res/drawable/bank.xml new file mode 100644 index 000000000..533165c3a --- /dev/null +++ b/app/src/main/res/drawable/bank.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/box.xml b/app/src/main/res/drawable/box.xml new file mode 100644 index 000000000..027a06f51 --- /dev/null +++ b/app/src/main/res/drawable/box.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_ok.xml b/app/src/main/res/drawable/btn_ok.xml new file mode 100644 index 000000000..179638fef --- /dev/null +++ b/app/src/main/res/drawable/btn_ok.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_ok_off.xml b/app/src/main/res/drawable/btn_ok_off.xml new file mode 100644 index 000000000..471115a5d --- /dev/null +++ b/app/src/main/res/drawable/btn_ok_off.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_ok_on.xml b/app/src/main/res/drawable/btn_ok_on.xml new file mode 100644 index 000000000..d659046c1 --- /dev/null +++ b/app/src/main/res/drawable/btn_ok_on.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/text_input_box_stroke.xml b/app/src/main/res/drawable/text_input_box_stroke.xml new file mode 100644 index 000000000..00c5088a4 --- /dev/null +++ b/app/src/main/res/drawable/text_input_box_stroke.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 000000000..b74969343 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,53 @@ + + + + + + + + + + +