Implemented retrieving and displaying account transactions

This commit is contained in:
dankito 2022-02-16 00:16:44 +01:00
parent a6a24311d7
commit a6fd9a4d42
15 changed files with 285 additions and 28 deletions

View File

@ -35,10 +35,17 @@ android {
} }
dependencies { dependencies {
implementation project(":fints4k")
implementation "net.dankito.utils:android-utils:1.1.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
implementation 'androidx.appcompat:appcompat:1.0.0' implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0-rc02' implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0-rc02'
implementation 'androidx.navigation:navigation-ui-ktx:2.0.0-rc02' implementation 'androidx.navigation:navigation-ui-ktx:2.0.0-rc02'
implementation "org.slf4j:slf4j-android:1.7.32"
} }

View File

@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.codinux.banking.fints4k.android"> package="net.codinux.banking.fints4k.android">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@ -1,11 +1,14 @@
package net.codinux.banking.fints4k.android package net.codinux.banking.fints4k.android
import android.os.Bundle import android.os.Bundle
import android.view.ContextThemeWrapper
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import net.codinux.banking.fints4k.android.adapter.AccountTransactionsListRecyclerAdapter
import net.codinux.banking.fints4k.android.databinding.FragmentFirstBinding import net.codinux.banking.fints4k.android.databinding.FragmentFirstBinding
/** /**
@ -15,6 +18,8 @@ class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null private var _binding: FragmentFirstBinding? = null
private val accountTransactionsAdapter = AccountTransactionsListRecyclerAdapter()
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
@ -32,8 +37,17 @@ class FirstFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.buttonFirst.setOnClickListener { binding.rcyvwAccountTransactions.apply {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) layoutManager = LinearLayoutManager(this@FirstFragment.context, LinearLayoutManager.VERTICAL, false)
addItemDecoration(DividerItemDecoration(ContextThemeWrapper(this@FirstFragment.context, R.style.Theme_Fints4kProject), (layoutManager as LinearLayoutManager).orientation))
adapter = accountTransactionsAdapter
}
// TODO: set your credentials here
Presenter().retrieveAccountData("", "", "", "") { response ->
if (response.successful) {
accountTransactionsAdapter.items = response.retrievedData.flatMap { it.bookedTransactions }
}
} }
} }

View File

@ -0,0 +1,54 @@
package net.codinux.banking.fints4k.android
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.FinTsClientDeprecated
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
import net.dankito.banking.fints.model.AddAccountParameter
import net.dankito.banking.fints.response.client.AddAccountResponse
import net.dankito.utils.multiplatform.extensions.millisSinceEpochAtSystemDefaultTimeZone
import org.slf4j.LoggerFactory
import java.math.BigDecimal
import java.text.DateFormat
import java.util.*
class Presenter {
companion object {
val ValueDateFormat = DateFormat.getDateInstance(DateFormat.SHORT)
private val log = LoggerFactory.getLogger(Presenter::class.java)
}
private val fintsClient = FinTsClientDeprecated(SimpleFinTsClientCallback())
fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (AddAccountResponse) -> Unit) {
fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) { response ->
log.info("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}")
GlobalScope.launch(Dispatchers.Main) {
retrievedResult(response)
}
}
}
fun formatDate(date: LocalDate): String {
try {
return ValueDateFormat.format(Date(date.millisSinceEpochAtSystemDefaultTimeZone))
} catch (e: Exception) {
log.error("Could not format date $date", e)
}
return date.toString()
}
fun formatAmount(amount: BigDecimal): String {
return String.format("%.02f", amount)
}
}

View File

@ -0,0 +1,46 @@
package net.codinux.banking.fints4k.android.adapter
import android.view.View
import net.codinux.banking.fints4k.android.Presenter
import net.codinux.banking.fints4k.android.R
import net.codinux.banking.fints4k.android.adapter.viewholder.AccountTransactionsViewHolder
import net.dankito.banking.fints.model.AccountTransaction
import net.dankito.banking.fints.util.toBigDecimal
import net.dankito.utils.android.extensions.setTextColorToColorResource
import net.dankito.utils.android.ui.adapter.ListRecyclerAdapter
import org.slf4j.LoggerFactory
import java.math.BigDecimal
class AccountTransactionsListRecyclerAdapter : ListRecyclerAdapter<AccountTransaction, AccountTransactionsViewHolder>() {
private val presenter = Presenter() // TOOD: inject
private val log = LoggerFactory.getLogger(AccountTransactionsListRecyclerAdapter::class.java)
override fun getListItemLayoutId() = R.layout.list_item_account_transaction
override fun createViewHolder(itemView: View): AccountTransactionsViewHolder {
return AccountTransactionsViewHolder(itemView)
}
override fun bindItemToView(viewHolder: AccountTransactionsViewHolder, item: AccountTransaction) {
try {
viewHolder.txtvwBookingText.text = item.bookingText ?: ""
viewHolder.txtvwOtherPartyName.visibility = if (item.showOtherPartyName) View.VISIBLE else View.GONE
viewHolder.txtvwOtherPartyName.text = item.otherPartyName ?: ""
viewHolder.txtvwReference.text = item.reference
viewHolder.txtvwDate.text = presenter.formatDate(item.valueDate)
val amount = item.amount.toBigDecimal()
viewHolder.txtvwAmount.text = presenter.formatAmount(amount)
viewHolder.txtvwAmount.setTextColorToColorResource(if (amount >= BigDecimal.ZERO) R.color.positiveAmount else R.color.negativeAmount)
} catch (e: Exception) {
log.error("Could not display account transaction $item", e)
}
}
}

View File

@ -0,0 +1,20 @@
package net.codinux.banking.fints4k.android.adapter.viewholder
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import net.codinux.banking.fints4k.android.R
class AccountTransactionsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txtvwBookingText: TextView = itemView.findViewById(R.id.txtvwBookingText)
val txtvwOtherPartyName: TextView = itemView.findViewById(R.id.txtvwOtherPartyName)
val txtvwReference: TextView = itemView.findViewById(R.id.txtvwReference)
val txtvwAmount: TextView = itemView.findViewById(R.id.txtvwAmount)
val txtvwDate: TextView = itemView.findViewById(R.id.txtvwDate)
}

View File

@ -4,6 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="@dimen/activity_content_padding"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment <fragment

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -7,25 +7,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".FirstFragment"> tools:context=".FirstFragment">
<TextView
android:id="@+id/textview_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_first_fragment"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/button_first"
/>
<Button <androidx.recyclerview.widget.RecyclerView android:id="@+id/rcyvwAccountTransactions"
android:id="@+id/button_first" android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_width="wrap_content" android:layout_alignParentTop="true"
android:layout_height="wrap_content" android:layout_alignParentBottom="true"
android:text="@string/next" >
app:layout_constraintTop_toBottomOf="@id/textview_first"
app:layout_constraintStart_toStartOf="parent" </androidx.recyclerview.widget.RecyclerView>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" </RelativeLayout>
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/list_item_account_transaction_height"
android:padding="@dimen/list_item_account_transaction_padding"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="@+id/lytAmountAndDate"
android:layout_toStartOf="@+id/lytAmountAndDate"
android:layout_marginLeft="@dimen/list_item_account_transaction_transaction_text_margin_left_and_right"
android:layout_marginStart="@dimen/list_item_account_transaction_transaction_text_margin_left_and_right"
android:layout_marginRight="@dimen/list_item_account_transaction_transaction_text_margin_left_and_right"
android:layout_marginEnd="@dimen/list_item_account_transaction_transaction_text_margin_left_and_right"
android:gravity="center_vertical"
>
<TextView
android:id="@+id/txtvwBookingText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/txtvwOtherPartyName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_item_account_transaction_other_party_name_margin_top_and_bottom"
android:layout_marginBottom="@dimen/list_item_account_transaction_other_party_name_margin_top_and_bottom"
/>
<TextView
android:id="@+id/txtvwReference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/lytAmountAndDate"
android:orientation="vertical"
android:layout_width="@dimen/list_item_account_transaction_amount_width"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/imgvwBankIcon"
android:layout_width="@dimen/list_item_account_transaction_bank_icon_width_and_height"
android:layout_height="@dimen/list_item_account_transaction_bank_icon_width_and_height"
android:layout_marginTop="@dimen/list_item_account_transaction_bank_icon_margin_top"
android:layout_marginBottom="@dimen/list_item_account_transaction_bank_icon_margin_bottom"
android:layout_gravity="end"
android:visibility="gone"
/>
<TextView
android:id="@+id/txtvwAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
/>
<TextView
android:id="@+id/txtvwDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_item_account_transaction_date_margin_top"
android:layout_marginBottom="@dimen/list_item_account_transaction_date_margin_bottom"
android:gravity="end"
/>
</LinearLayout>
</RelativeLayout>

View File

@ -7,4 +7,9 @@
<color name="teal_700">#FF018786</color> <color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="positiveAmount">#43A047</color>
<color name="negativeAmount">#E53935</color>
</resources> </resources>

View File

@ -1,3 +1,18 @@
<resources> <resources>
<dimen name="activity_content_padding">4dp</dimen>
<dimen name="fab_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen>
<dimen name="list_item_account_transaction_height">100dp</dimen>
<dimen name="list_item_account_transaction_padding">4dp</dimen>
<dimen name="list_item_account_transaction_bank_icon_width_and_height">16dp</dimen>
<dimen name="list_item_account_transaction_bank_icon_margin_top">8dp</dimen>
<dimen name="list_item_account_transaction_bank_icon_margin_bottom">12dp</dimen>
<dimen name="list_item_account_transaction_amount_width">70dp</dimen>
<dimen name="list_item_account_transaction_date_margin_top">12dp</dimen>
<dimen name="list_item_account_transaction_date_margin_bottom">8dp</dimen>
<dimen name="list_item_account_transaction_transaction_text_margin_left_and_right">4dp</dimen>
<dimen name="list_item_account_transaction_other_party_name_margin_top_and_bottom">6dp</dimen>
</resources> </resources>

View File

@ -75,8 +75,6 @@ kotlin {
dependencies { dependencies {
api project(":multiplatform-utils") api project(":multiplatform-utils")
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
implementation "co.touchlab:stately-concurrency:1.2.0" implementation "co.touchlab:stately-concurrency:1.2.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"

View File

@ -54,6 +54,9 @@ open class AccountTransaction(
null, "", "", null, null, "", null) null, "", "", null, null, "", null)
open val showOtherPartyName: Boolean
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
val reference: String val reference: String
get() = sepaReference ?: unparsedReference get() = sepaReference ?: unparsedReference

View File

@ -0,0 +1,14 @@
package net.dankito.banking.fints.util
import net.dankito.banking.fints.model.Amount
import net.dankito.banking.fints.model.Money
import java.math.BigDecimal
fun Amount.toBigDecimal(): BigDecimal {
return BigDecimal(this.string.replace(',', '.'))
}
fun Money.toBigDecimal(): BigDecimal {
return this.amount.toBigDecimal()
}

View File

@ -65,7 +65,7 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2" api "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
} }
} }