diff --git a/Project/.gitignore b/Project/.gitignore new file mode 100644 index 000000000..603b14077 --- /dev/null +++ b/Project/.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/Project/.idea/.name b/Project/.idea/.name new file mode 100644 index 000000000..d5d9a0714 --- /dev/null +++ b/Project/.idea/.name @@ -0,0 +1 @@ +BankApp \ No newline at end of file diff --git a/Project/.idea/codeStyles/Project.xml b/Project/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..45b565415 --- /dev/null +++ b/Project/.idea/codeStyles/Project.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + +
+ + + + 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/Project/.idea/codeStyles/codeStyleConfig.xml b/Project/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/Project/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/Project/.idea/gradle.xml b/Project/.idea/gradle.xml new file mode 100644 index 000000000..169fd0dd9 --- /dev/null +++ b/Project/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/Project/.idea/misc.xml b/Project/.idea/misc.xml new file mode 100644 index 000000000..faf413cce --- /dev/null +++ b/Project/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Project/.idea/runConfigurations.xml b/Project/.idea/runConfigurations.xml new file mode 100644 index 000000000..7f68460d8 --- /dev/null +++ b/Project/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/Project/.idea/vcs.xml b/Project/.idea/vcs.xml new file mode 100644 index 000000000..6c0b86358 --- /dev/null +++ b/Project/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Project/app/.gitignore b/Project/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/Project/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/Project/app/build.gradle b/Project/app/build.gradle new file mode 100644 index 000000000..6b875da29 --- /dev/null +++ b/Project/app/build.gradle @@ -0,0 +1,49 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.1" + defaultConfig { + applicationId "projects.kevin.bankapp" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-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 "androidx.recyclerview:recyclerview:1.1.0" + testImplementation 'junit:junit:4.12' + + implementation 'com.squareup.retrofit2:retrofit:2.5.0' + + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0' + + implementation 'com.afollestad.material-dialogs:core:3.3.0' + implementation "androidx.cardview:cardview:1.0.0" + + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation 'com.squareup.retrofit2:converter-gson:2.5.0' + + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} diff --git a/Project/app/proguard-rules.pro b/Project/app/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/Project/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/Project/app/src/androidTest/java/projects/kevin/bankapp/ExampleInstrumentedTest.kt b/Project/app/src/androidTest/java/projects/kevin/bankapp/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..1242f91f0 --- /dev/null +++ b/Project/app/src/androidTest/java/projects/kevin/bankapp/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package projects.kevin.bankapp + +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("projects.kevin.bankapp", appContext.packageName) + } +} diff --git a/Project/app/src/androidTest/java/projects/kevin/bankapp/FormatTest.kt b/Project/app/src/androidTest/java/projects/kevin/bankapp/FormatTest.kt new file mode 100644 index 000000000..ae1115211 --- /dev/null +++ b/Project/app/src/androidTest/java/projects/kevin/bankapp/FormatTest.kt @@ -0,0 +1,45 @@ +package projects.kevin.bankapp + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.TestCase +import org.junit.Test +import org.junit.runner.RunWith +import projects.kevin.bankapp.utils.parseDate +import projects.kevin.bankapp.utils.turnToPositiveValue +import java.math.BigDecimal +import java.text.ParseException + +@RunWith(AndroidJUnit4::class) +public class FormatTest: TestCase() { + + @Test + fun testEmptyLogin() { + + /* 0 = "Both values are equal " + 1 = "First Value is greater " + -1 = "Second value is greater"*/ + + val positiveGreater = turnToPositiveValue(BigDecimal("-54.90")) + val finalValue = positiveGreater.compareTo(BigDecimal(0)) + assertTrue( finalValue == 1) + + val equalValue = turnToPositiveValue(BigDecimal("0.00")) + val finalValue2 = equalValue.compareTo(BigDecimal(0)) + assertTrue( finalValue2 == 0) + + val lowerValue = turnToPositiveValue(BigDecimal("-4.90")) + val cp = lowerValue.compareTo(BigDecimal(5)) + assertTrue( cp == -1) + } + + @Test(expected = ParseException::class) + fun testParseDateError() { + assert(parseDate("27/11/1885") == "27-11-1885") + } + + @Test + fun testParseDateSuccess() { + assert(parseDate("2018-06-23") == "23/06/2018") + } + +} diff --git a/Project/app/src/androidTest/java/projects/kevin/bankapp/LoginTest.kt b/Project/app/src/androidTest/java/projects/kevin/bankapp/LoginTest.kt new file mode 100644 index 000000000..f0ec17e6a --- /dev/null +++ b/Project/app/src/androidTest/java/projects/kevin/bankapp/LoginTest.kt @@ -0,0 +1,48 @@ +package projects.kevin.bankapp + +import junit.framework.TestCase +import projects.kevin.bankapp.utils.validateLogin + +public class LoginTest: TestCase() { + + fun testEmptyLogin() { + assertFalse(validateLogin("123@A!", "")) + assertFalse(validateLogin("23@A!", "")) + assertFalse(validateLogin("3@A!", "")) + assertFalse(validateLogin("@A!", "")) + } + + fun testMinimumCharacter() { + assertTrue(validateLogin("123@A!", "a")) + assertTrue(validateLogin("3@A!", "aaa")) + + assertFalse(validateLogin("23@A!", "")) + assertFalse(validateLogin("!", "aa")) + } + + fun testEmptyPassword() { + assertFalse(validateLogin("", "user_test@email.com")) + assertFalse(validateLogin("", "user_test@email")) + assertFalse(validateLogin("", "user_test@")) + assertFalse(validateLogin("", "user")) + } + + fun testNoUpperCasePassword() { + assertFalse(validateLogin("123@!", "user")) + assertFalse(validateLogin("12@!", "user@email")) + assertFalse(validateLogin("1@!", "suer@email.com")) + } + + fun testNoSpecialCharacterPassword() { + assertFalse(validateLogin("123A", "user")) + assertFalse(validateLogin("12A", "user@email")) + assertFalse(validateLogin("1A", "user@email.com")) + } + + fun testCorrectPassEmail() { + assertTrue(validateLogin("@A", "44455566688")) + assertTrue(validateLogin("aBc!", "user@email")) + assertTrue(validateLogin("23A&32", "user@email.com")) + } + +} diff --git a/Project/app/src/main/AndroidManifest.xml b/Project/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ddfdb7ef7 --- /dev/null +++ b/Project/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/base/BaseActivity.kt b/Project/app/src/main/java/projects/kevin/bankapp/base/BaseActivity.kt new file mode 100644 index 000000000..b30bc6314 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/base/BaseActivity.kt @@ -0,0 +1,25 @@ +package projects.kevin.bankapp.base + +import androidx.appcompat.app.AppCompatActivity + + + + +abstract class BaseActivity : AppCompatActivity() { + + var active = false + + public override fun onStart() { + super.onStart() + active = true + } + + public override fun onStop() { + super.onStop() + active = false + } + + fun isActive(): Boolean { + return active + } +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/base/BasePresenter.kt b/Project/app/src/main/java/projects/kevin/bankapp/base/BasePresenter.kt new file mode 100644 index 000000000..a183408dc --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/base/BasePresenter.kt @@ -0,0 +1,34 @@ +package projects.kevin.bankapp.base + +import androidx.appcompat.app.AppCompatActivity +import com.afollestad.materialdialogs.MaterialDialog +import projects.kevin.bankapp.user.service.qualifier.PostThreadExecutor +import projects.kevin.bankapp.user.service.qualifier.ThreadExecutor +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers + + +/** + * Created by caporal on 01/08/18. + */ +open class BasePresenter constructor() { + + + protected var compositeDisposable = CompositeDisposable() + + open fun destroy() { + compositeDisposable.dispose() + } + + + protected fun execute(single: Single): Single { + return single + .subscribeOn(ThreadExecutor(Schedulers.io()).scheduler) + .observeOn(PostThreadExecutor(AndroidSchedulers.mainThread()).scheduler) + } + + +} + diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailActivity.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailActivity.kt new file mode 100644 index 000000000..21c7ffbe5 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailActivity.kt @@ -0,0 +1,105 @@ +package projects.kevin.bankapp.user.detail + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View.GONE +import android.view.View.VISIBLE +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.synthetic.main.activity_detail.* +import kotlinx.android.synthetic.main.header_user_detail.* +import projects.kevin.bankapp.R +import projects.kevin.bankapp.base.BaseActivity +import projects.kevin.bankapp.user.login.UserAccount +import projects.kevin.bankapp.user.sharedPref.UserDataSharedPref +import projects.kevin.bankapp.utils.formatMoney +import projects.kevin.bankapp.utils.validateMaterialDialog + +class DetailActivity : BaseActivity(), DetailView { + + private lateinit var presenter: DetailPresenter + private lateinit var userPreferences: UserDataSharedPref + + companion object { + const val MONEY_TYPE = "R$" + + fun startDetail(activity: Activity) { + val start = Intent(activity, DetailActivity::class.java) + activity.startActivity(start) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_detail) + + presenter = DetailPresenter(this) + userPreferences = UserDataSharedPref(this) + + logoutBtnAccount.setOnClickListener { + onLogoutClick() + } + } + + override fun onDestroy() { + super.onDestroy() + presenter.destroy() + } + + override fun onResume() { + super.onResume() + presenter.loadUserData(userPreferences) + } + + override fun loadStatements(bankStatements: ArrayList?) { + initStatementRecycler(bankStatements!!) + hideLoading() + } + + override fun showLoading() { + loadingStatements.visibility = VISIBLE + } + + override fun hideLoading() { + loadingStatements.visibility = GONE + } + + @SuppressLint("SetTextI18n") + override fun loadUserAccountPreferences(userAccount: UserAccount) { + with(userAccount) { + presenter.fetchUserStatements(userId) + nameDetailAccount.text = name + agencyDetailAccount.text = "$bankAccount ${getString(R.string.account_agency_separator)} $agency" + balanceDetailAccount.text = "$MONEY_TYPE${formatMoney(balance)}" + } + } + + private fun onLogoutClick() { + validateMaterialDialog(this)?.show { + title(res = R.string.logout_dialog_title) + message(res = R.string.logout_dialog_desc) + cancelable(true) + cornerRadius(literalDp = 8f) + positiveButton(res = R.string.yes) { dialog -> + userPreferences.clearPreferences() + this@DetailActivity.finish() + dialog.dismiss() + } + negativeButton(res = R.string.no) { dialog -> + dialog.dismiss() + } + } + + } + + private fun initStatementRecycler(bankStatements: ArrayList) { + val fastingTipsAdapter = StatementsAdapter() + val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + bankStatementRecyclerView.layoutManager = layoutManager + bankStatementRecyclerView.adapter = fastingTipsAdapter + bankStatementRecyclerView.setHasFixedSize(false) + fastingTipsAdapter.setListView(bankStatements) + } +} diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailModel.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailModel.kt new file mode 100644 index 000000000..93f67db6e --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailModel.kt @@ -0,0 +1,14 @@ +package projects.kevin.bankapp.user.detail + +import java.math.BigDecimal + +data class BankStatements( + val value: BigDecimal, + val title: String, + val desc: String, + val date: String +) + +data class DetailApiResponse( + val statementList: ArrayList? = null +) diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailPresenter.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailPresenter.kt new file mode 100644 index 000000000..dc339a5e4 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailPresenter.kt @@ -0,0 +1,33 @@ +package projects.kevin.bankapp.user.detail + +import projects.kevin.bankapp.user.service.APIClient +import projects.kevin.bankapp.base.BasePresenter +import projects.kevin.bankapp.user.login.UserAccount +import projects.kevin.bankapp.user.sharedPref.UserDataSharedPref +import java.math.BigDecimal + +class DetailPresenter(private val view: DetailView): BasePresenter() { + + fun loadUserData(userPreferences: UserDataSharedPref) { + view.loadUserAccountPreferences ( + UserAccount(userId = userPreferences.getUserId()?.toLong()!!, + name = userPreferences.getName()!!, + bankAccount = userPreferences.getBankAccount()!!, + agency = userPreferences.getAgency()!!, + balance = BigDecimal(userPreferences.getBalance()!!) + ) + ) + } + + fun fetchUserStatements(userId: Long) { + view.showLoading() + APIClient.getReactiveService()?.let { apiUserService -> + execute(apiUserService.fetchUserDetail(userId)).subscribe( { detailApiResponse-> + view.loadStatements(detailApiResponse.statementList) + }, { throwable: Throwable? -> + view.hideLoading() + }) + } + } + +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailView.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailView.kt new file mode 100644 index 000000000..75337153c --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/DetailView.kt @@ -0,0 +1,11 @@ +package projects.kevin.bankapp.user.detail + +import projects.kevin.bankapp.user.login.UserAccount + +interface DetailView { + + fun loadUserAccountPreferences(userAccount: UserAccount) + fun loadStatements(bankStatements: ArrayList?) + fun showLoading() + fun hideLoading() +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/detail/StatementsAdapter.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/StatementsAdapter.kt new file mode 100644 index 000000000..2065eabdf --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/detail/StatementsAdapter.kt @@ -0,0 +1,47 @@ +package projects.kevin.bankapp.user.detail + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.item_bank_statement.view.* +import projects.kevin.bankapp.R +import projects.kevin.bankapp.utils.formatMoney +import projects.kevin.bankapp.utils.parseDate +import projects.kevin.bankapp.utils.turnToPositiveValue + +class StatementsAdapter: RecyclerView.Adapter() { + + private var statementList: MutableList = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return DietItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_bank_statement, parent, false)) + } + + fun setListView(statementList: ArrayList) { + this.statementList.addAll(statementList) + notifyDataSetChanged() + } + + override fun getItemCount(): Int { + return statementList.size + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + (holder as DietItemViewHolder).bind(statementList[position]) + } + + class DietItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), LayoutContainer { + override val containerView = itemView + fun bind(items: BankStatements) { + with(items) { + itemView.statementDate.text = parseDate(date) + itemView.statementDescription.text = desc + itemView.statementName.text = title + itemView.statementValue.text = "${DetailActivity.MONEY_TYPE}${formatMoney(turnToPositiveValue(value))}" + } + } + } + +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginActivity.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginActivity.kt new file mode 100644 index 000000000..79b44a6ad --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginActivity.kt @@ -0,0 +1,122 @@ +package projects.kevin.bankapp.user.login + +import android.os.Bundle +import android.text.SpannableString +import android.text.style.UnderlineSpan +import android.view.View.GONE +import android.view.View.VISIBLE +import android.widget.Toast +import kotlinx.android.synthetic.main.activity_login.* +import projects.kevin.bankapp.R +import projects.kevin.bankapp.base.BaseActivity +import projects.kevin.bankapp.user.detail.DetailActivity +import projects.kevin.bankapp.user.sharedPref.UserDataSharedPref +import projects.kevin.bankapp.utils.validateLogin + + +class LoginActivity : BaseActivity(), LoginView { + + private lateinit var presenter: LoginPresenter + private lateinit var userPreferences: UserDataSharedPref + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_login) + + presenter = LoginPresenter(view = this) + userPreferences = UserDataSharedPref(this) + + buttonLogin.setOnClickListener { + val password = passLoginText.text.toString() + val login = userLoginText.text.toString() + if(validateLogin(password, login)) { + val credentials = LoginApiRequest(login, password) + presenter.userLogin(credentials) + } else { + Toast.makeText(this, getString(R.string.incorrect_password), Toast.LENGTH_LONG).show() + } + } + } + + override fun onResume() { + super.onResume() + if(userPreferences.getName() != null) { + loggedState(userPreferences.getName()!!) + } else { + logoutState() + } + } + + override fun onDestroy() { + super.onDestroy() + presenter.destroy() + } + + override fun onLoginSuccess(userAccount: UserAccount) { + presenter.saveOnShared(userAccount, userPreferences) + + } + + override fun onLoginFailed() { + if(isActive()) { + Toast.makeText(this, getString(R.string.incorrect_password), Toast.LENGTH_LONG).show() + } + } + + override fun onSaveUserData() { + DetailActivity.startDetail(this) + } + + override fun hideLoading() { + progressLayout.visibility = GONE + } + + override fun showLoading() { + progressLayout.visibility = VISIBLE + progressLayout.bringToFront() + } + + override fun onRequestFailed() { + if(isActive()) { + Toast.makeText(this, getString(R.string.error_try_again), Toast.LENGTH_LONG).show() + } + } + + private fun loggedState(name: String) { + buttonLogin.visibility = GONE + userLoginText.visibility = GONE + passLoginText.visibility = GONE + SingInButton.visibility = VISIBLE + singInLabel.visibility = VISIBLE + alreadyLoggedName.visibility = VISIBLE + + formatLoggedText(name) + loggedStateButtons() + } + + private fun formatLoggedText(name: String) { + val content = SpannableString("${getString(R.string.login_screen_logout)} $name") + content.setSpan(UnderlineSpan(), 0, content.length, 0) + alreadyLoggedName.text = content + SingInButton.text = name + } + + private fun loggedStateButtons() { + SingInButton.setOnClickListener { + DetailActivity.startDetail(this) + } + alreadyLoggedName.setOnClickListener { + userPreferences.clearPreferences() + logoutState() + } + } + + private fun logoutState() { + SingInButton.visibility = GONE + singInLabel.visibility = GONE + alreadyLoggedName.visibility = GONE + buttonLogin.visibility = VISIBLE + userLoginText.visibility = VISIBLE + passLoginText.visibility = VISIBLE + } +} diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginPresenter.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginPresenter.kt new file mode 100644 index 000000000..ba5a12ecb --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginPresenter.kt @@ -0,0 +1,38 @@ +package projects.kevin.bankapp.user.login + +import projects.kevin.bankapp.user.service.APIClient +import projects.kevin.bankapp.base.BasePresenter +import projects.kevin.bankapp.user.sharedPref.UserDataSharedPref + +class LoginPresenter(private val view: LoginView): BasePresenter() { + + fun userLogin(credentials: LoginApiRequest) { + view.showLoading() + APIClient.getReactiveService()?.let { apiUserService -> + execute(apiUserService.login(credentials)).subscribe( { loginResponse -> + if(loginResponse?.error?.code != null) { + view.onLoginFailed() + } else { + view.onLoginSuccess(loginResponse.userAccount) + } + }, { throwable: Throwable? -> + view.onRequestFailed() + view.hideLoading() + }) + } + } + + fun saveOnShared(userData: UserAccount, userPreferences: UserDataSharedPref) { + with(userData) { + userPreferences.persistAgency(agency) + userPreferences.persistBalance(balance.toString()) + userPreferences.persistName(name) + userPreferences.persistBankAccount(bankAccount) + userPreferences.persistUserId(userId.toString()) + } + view.hideLoading() + view.onSaveUserData() + + } + +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginView.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginView.kt new file mode 100644 index 000000000..287d440fb --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/login/LoginView.kt @@ -0,0 +1,11 @@ +package projects.kevin.bankapp.user.login + +interface LoginView { + + fun onLoginSuccess(userAccount: UserAccount) + fun onLoginFailed() + fun onSaveUserData() + fun showLoading() + fun hideLoading() + fun onRequestFailed() +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/login/loginModel.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/login/loginModel.kt new file mode 100644 index 000000000..ab92edf03 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/login/loginModel.kt @@ -0,0 +1,27 @@ +package projects.kevin.bankapp.user.login + +import java.math.BigDecimal + +data class UserAccount( + val userId: Long, + val name: String, + val bankAccount: String, + val agency: String, + val balance: BigDecimal + +) + +data class LoginApiResponse( + val userAccount: UserAccount, + val error: Error? = null +) + +data class LoginApiRequest( + val user: String, + val password: String +) + +data class Error( + val code: String, + val message: String +) \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/service/APIClient.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/service/APIClient.kt new file mode 100644 index 000000000..8434c28ca --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/service/APIClient.kt @@ -0,0 +1,34 @@ +package projects.kevin.bankapp.user.service + +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory + +class APIClient { + + companion object { + + private var protocol: ApiUserService? = null + + fun getReactiveService(): ApiUserService? { + if (protocol == null){ + + protocol = try { + val retrofit = Retrofit.Builder() + .baseUrl("https://bank-app-test.herokuapp.com/api/") + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build() + + retrofit.create(ApiUserService::class.java) + }catch (e: IllegalArgumentException){ + null + } + + } + return protocol + } + + + } +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/service/ApiUserService.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/service/ApiUserService.kt new file mode 100644 index 000000000..2a6561c43 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/service/ApiUserService.kt @@ -0,0 +1,16 @@ +package projects.kevin.bankapp.user.service + +import io.reactivex.Single +import projects.kevin.bankapp.user.detail.DetailApiResponse +import projects.kevin.bankapp.user.login.LoginApiRequest +import projects.kevin.bankapp.user.login.LoginApiResponse +import retrofit2.http.* + +interface ApiUserService { + + @POST("login") + fun login(@Body configs: LoginApiRequest): Single + + @GET("statements/{userId}") + fun fetchUserDetail(@Path("userId") userId: Long): Single +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/service/qualifier/PostThreadExecutor.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/service/qualifier/PostThreadExecutor.kt new file mode 100644 index 000000000..ae2ecf0d0 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/service/qualifier/PostThreadExecutor.kt @@ -0,0 +1,5 @@ +package projects.kevin.bankapp.user.service.qualifier + +import io.reactivex.Scheduler + +class PostThreadExecutor(val scheduler: Scheduler) \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/service/qualifier/ThreadExecutor.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/service/qualifier/ThreadExecutor.kt new file mode 100644 index 000000000..68f1be957 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/service/qualifier/ThreadExecutor.kt @@ -0,0 +1,5 @@ +package projects.kevin.bankapp.user.service.qualifier + +import io.reactivex.Scheduler + +class ThreadExecutor(val scheduler: Scheduler) \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/user/sharedPref/UserData.kt b/Project/app/src/main/java/projects/kevin/bankapp/user/sharedPref/UserData.kt new file mode 100644 index 000000000..ad86fb052 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/user/sharedPref/UserData.kt @@ -0,0 +1,72 @@ +package projects.kevin.bankapp.user.sharedPref + +import android.content.Context +import android.content.SharedPreferences + +class UserDataSharedPref(private val context: Context) { + + companion object { + private const val BANK_ACCOUNT = "BANK_ACCOUNT" + private const val AGENCY = "AGENCY" + private const val USER_ID = "USER_ID" + private const val BALANCE = "BALANCE" + private const val NAME = "NAME" + } + + private fun getPreferences(): SharedPreferences { + return context.getSharedPreferences("userData", Context.MODE_PRIVATE)!! + } + + private fun getPreferencesEdit(): SharedPreferences.Editor { + return getPreferences().edit() + } + + fun clearPreferences() { + val editor = getPreferencesEdit() + editor.clear() + editor.apply() + } + + ///SET + fun persistUserId(userId: String) { + getPreferencesEdit().putString(USER_ID, userId).commit() + } + + fun persistName(name: String) { + getPreferencesEdit().putString(NAME, name).commit() + } + + fun persistBankAccount(bankAccount: String) { + getPreferencesEdit().putString(BANK_ACCOUNT, bankAccount).commit() + } + + fun persistAgency(agency: String) { + getPreferencesEdit().putString(AGENCY, agency).commit() + } + + fun persistBalance(balance: String) { + getPreferencesEdit().putString(BALANCE, balance).commit() + } + + ///GET + fun getUserId(): String? { + return getPreferences().getString(USER_ID, null) + } + + fun getName(): String? { + return getPreferences().getString(NAME, null) + } + + fun getBankAccount(): String? { + return getPreferences().getString(BANK_ACCOUNT, null) + } + + fun getAgency(): String? { + return getPreferences().getString(AGENCY, null) + } + + fun getBalance(): String? { + return getPreferences().getString(BALANCE, null) + } + +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/utils/FormatValues.kt b/Project/app/src/main/java/projects/kevin/bankapp/utils/FormatValues.kt new file mode 100644 index 000000000..2945e280f --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/utils/FormatValues.kt @@ -0,0 +1,26 @@ +package projects.kevin.bankapp.utils + +import android.annotation.SuppressLint +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.SimpleDateFormat + +fun formatMoney(balance: BigDecimal): String { + val numberFormat = java.text.DecimalFormat(",#00.00#") + return numberFormat.format(balance.setScale(2, RoundingMode.HALF_UP)) +} + +fun turnToPositiveValue(value: BigDecimal): BigDecimal { + if(value < BigDecimal(0)) { + return value.multiply(BigDecimal(-1)) + } + + return value +} + +@SuppressLint("SimpleDateFormat") +fun parseDate(dateString: String): String { + val initDate = SimpleDateFormat("yyyy-MM-dd").parse(dateString) + val formatter = SimpleDateFormat("dd/MM/yyyy") + return formatter.format(initDate) +} \ No newline at end of file diff --git a/Project/app/src/main/java/projects/kevin/bankapp/utils/Validate.kt b/Project/app/src/main/java/projects/kevin/bankapp/utils/Validate.kt new file mode 100644 index 000000000..7cba7ebf1 --- /dev/null +++ b/Project/app/src/main/java/projects/kevin/bankapp/utils/Validate.kt @@ -0,0 +1,35 @@ +package projects.kevin.bankapp.utils + +import androidx.appcompat.app.AppCompatActivity +import com.afollestad.materialdialogs.MaterialDialog + + +fun validateLogin(password: String, login: String): Boolean { + val regexAlpha = "[a-zA-z0-9]".toRegex() + val regexSpecial = "[!@#\$%^&()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]".toRegex() + val regexUpper = "[A-Z]".toRegex() + + if(login.isEmpty() || password.length < 2) { + return false + } + + if(regexAlpha.containsMatchIn(password)) { + if(regexUpper.containsMatchIn(password)) { + if(regexSpecial.containsMatchIn(password)) { + return true + } + } + } + + return false +} + +fun validateMaterialDialog(activity: AppCompatActivity?): MaterialDialog? { + if (activity?.isFinishing == false) { + return MaterialDialog(activity) + } + + return null +} + + diff --git a/Project/app/src/main/res/drawable-hdpi/logo.png b/Project/app/src/main/res/drawable-hdpi/logo.png new file mode 100644 index 000000000..557de15c3 Binary files /dev/null and b/Project/app/src/main/res/drawable-hdpi/logo.png differ diff --git a/Project/app/src/main/res/drawable-hdpi/logout.png b/Project/app/src/main/res/drawable-hdpi/logout.png new file mode 100644 index 000000000..2fa150d79 Binary files /dev/null and b/Project/app/src/main/res/drawable-hdpi/logout.png differ diff --git a/Project/app/src/main/res/drawable-mdpi/logo.png b/Project/app/src/main/res/drawable-mdpi/logo.png new file mode 100644 index 000000000..eebbca80a Binary files /dev/null and b/Project/app/src/main/res/drawable-mdpi/logo.png differ diff --git a/Project/app/src/main/res/drawable-mdpi/logout.png b/Project/app/src/main/res/drawable-mdpi/logout.png new file mode 100644 index 000000000..3bc9c37ca Binary files /dev/null and b/Project/app/src/main/res/drawable-mdpi/logout.png differ diff --git a/Project/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Project/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..1f6bb2906 --- /dev/null +++ b/Project/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/Project/app/src/main/res/drawable-xhdpi/logo.png b/Project/app/src/main/res/drawable-xhdpi/logo.png new file mode 100644 index 000000000..df7bdc1d4 Binary files /dev/null and b/Project/app/src/main/res/drawable-xhdpi/logo.png differ diff --git a/Project/app/src/main/res/drawable-xhdpi/logout.png b/Project/app/src/main/res/drawable-xhdpi/logout.png new file mode 100644 index 000000000..9f35dcb71 Binary files /dev/null and b/Project/app/src/main/res/drawable-xhdpi/logout.png differ diff --git a/Project/app/src/main/res/drawable-xxhdpi/logo.png b/Project/app/src/main/res/drawable-xxhdpi/logo.png new file mode 100644 index 000000000..141d89a70 Binary files /dev/null and b/Project/app/src/main/res/drawable-xxhdpi/logo.png differ diff --git a/Project/app/src/main/res/drawable-xxhdpi/logout.png b/Project/app/src/main/res/drawable-xxhdpi/logout.png new file mode 100644 index 000000000..49ada6177 Binary files /dev/null and b/Project/app/src/main/res/drawable-xxhdpi/logout.png differ diff --git a/Project/app/src/main/res/drawable-xxxhdpi/logo.png b/Project/app/src/main/res/drawable-xxxhdpi/logo.png new file mode 100644 index 000000000..b010bd662 Binary files /dev/null and b/Project/app/src/main/res/drawable-xxxhdpi/logo.png differ diff --git a/Project/app/src/main/res/drawable-xxxhdpi/logout.png b/Project/app/src/main/res/drawable-xxxhdpi/logout.png new file mode 100644 index 000000000..963ea7668 Binary files /dev/null and b/Project/app/src/main/res/drawable-xxxhdpi/logout.png differ diff --git a/Project/app/src/main/res/drawable/bg_btn_login.xml b/Project/app/src/main/res/drawable/bg_btn_login.xml new file mode 100644 index 000000000..809c67868 --- /dev/null +++ b/Project/app/src/main/res/drawable/bg_btn_login.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Project/app/src/main/res/drawable/bg_login_edit_text.xml b/Project/app/src/main/res/drawable/bg_login_edit_text.xml new file mode 100644 index 000000000..2899bb957 --- /dev/null +++ b/Project/app/src/main/res/drawable/bg_login_edit_text.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Project/app/src/main/res/drawable/bg_logo_bank.xml b/Project/app/src/main/res/drawable/bg_logo_bank.xml new file mode 100644 index 000000000..5e5660a60 --- /dev/null +++ b/Project/app/src/main/res/drawable/bg_logo_bank.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Project/app/src/main/res/drawable/ic_launcher_background.xml b/Project/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..0d025f9bf --- /dev/null +++ b/Project/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/app/src/main/res/layout/activity_detail.xml b/Project/app/src/main/res/layout/activity_detail.xml new file mode 100644 index 000000000..a2cb9b2f5 --- /dev/null +++ b/Project/app/src/main/res/layout/activity_detail.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Project/app/src/main/res/layout/activity_login.xml b/Project/app/src/main/res/layout/activity_login.xml new file mode 100644 index 000000000..de87873c2 --- /dev/null +++ b/Project/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + +