diff --git a/BankingJavaFxApp/build.gradle b/BankingJavaFxApp/build.gradle index 3d1bc538..e084660d 100644 --- a/BankingJavaFxApp/build.gradle +++ b/BankingJavaFxApp/build.gradle @@ -19,9 +19,11 @@ def title = 'Banking' dependencies { - api project(':BankingJavaFxControls') + implementation project(':BankingJavaFxControls') - api project(':fints4javaBankingClient') + implementation project(':fints4javaBankingClient') + + implementation project(':BankingPersistenceJson') implementation "ch.qos.logback:logback-classic:$logbackVersion" } diff --git a/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt b/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt index fad17239..b4012caa 100755 --- a/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt +++ b/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt @@ -2,6 +2,7 @@ package net.dankito.banking.javafx.dialogs.mainwindow import javafx.scene.control.SplitPane import net.dankito.banking.fints4javaBankingClientCreator +import net.dankito.banking.persistence.BankingPersistenceJson import net.dankito.banking.ui.javafx.RouterJavaFx import net.dankito.banking.ui.javafx.controls.AccountTransactionsView import net.dankito.banking.ui.javafx.controls.AccountsView @@ -10,11 +11,12 @@ import net.dankito.banking.ui.javafx.util.Base64ServiceJava8 import net.dankito.banking.ui.presenter.MainWindowPresenter import tornadofx.* import tornadofx.FX.Companion.messages +import java.io.File class MainWindow : View(messages["application.title"]) { - private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), Base64ServiceJava8(), RouterJavaFx()) + private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File("data/accounts.json")), Base64ServiceJava8(), RouterJavaFx()) diff --git a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt index 9a64004b..db8514bd 100644 --- a/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt +++ b/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt @@ -23,7 +23,7 @@ open class AccountTransactionsView(private val presenter: MainWindowPresenter) : protected val balance = SimpleStringProperty("") - protected val transactionsToDisplay = FXCollections.observableArrayList(presenter.allTransactions) + protected val transactionsToDisplay = FXCollections.observableArrayList(listOf()) protected var currentMenu: ContextMenu? = null @@ -37,6 +37,8 @@ open class AccountTransactionsView(private val presenter: MainWindowPresenter) : } transactionsFilter.addListener { _, _, newValue -> updateTransactionsToDisplay(newValue) } + + handleSelectedBankAccountsChanged(presenter.selectedBankAccounts) // so that isAccountSelected and transactionsToDisplay get set } diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/persistence/IBankingPersistence.kt b/BankingUiCommon/src/main/java/net/dankito/banking/persistence/IBankingPersistence.kt new file mode 100644 index 00000000..8bc002eb --- /dev/null +++ b/BankingUiCommon/src/main/java/net/dankito/banking/persistence/IBankingPersistence.kt @@ -0,0 +1,12 @@ +package net.dankito.banking.persistence + +import net.dankito.banking.ui.model.Account + + +interface IBankingPersistence { + + fun saveOrUpdateAccount(account: Account, allAccounts: List) + + fun readPersistedAccounts(): List + +} \ No newline at end of file diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/Account.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/Account.kt index a3ba5eac..99815622 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/Account.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/Account.kt @@ -1,11 +1,14 @@ package net.dankito.banking.ui.model +import com.fasterxml.jackson.annotation.JsonIdentityInfo +import com.fasterxml.jackson.annotation.ObjectIdGenerators import net.dankito.banking.ui.model.tan.TanMedium import net.dankito.banking.ui.model.tan.TanMediumStatus import net.dankito.banking.ui.model.tan.TanProcedure import java.math.BigDecimal +@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator::class) // to avoid stack overflow due to circular references // TODO: remove again, add custom domain object open class Account( val bank: Bank, val customerId: String, @@ -15,6 +18,7 @@ open class Account( var bankAccounts: List = listOf() ) { + internal constructor() : this(Bank(), "", "", "") // for object deserializers diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/AccountTransaction.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/AccountTransaction.kt index ec082513..8ba1e5fe 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/AccountTransaction.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/AccountTransaction.kt @@ -1,10 +1,13 @@ package net.dankito.banking.ui.model +import com.fasterxml.jackson.annotation.JsonIdentityInfo +import com.fasterxml.jackson.annotation.ObjectIdGenerators import java.math.BigDecimal import java.text.DateFormat import java.util.* +@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator::class) // to avoid stack overflow due to circular references // TODO: remove again, add custom domain object open class AccountTransaction( val amount: BigDecimal, val bookingDate: Date, diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/BankAccount.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/BankAccount.kt index 0cc270a6..d064a1b7 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/BankAccount.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/BankAccount.kt @@ -1,8 +1,11 @@ package net.dankito.banking.ui.model +import com.fasterxml.jackson.annotation.JsonIdentityInfo +import com.fasterxml.jackson.annotation.ObjectIdGenerators import java.math.BigDecimal +@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator::class) // to avoid stack overflow due to circular references // TODO: remove again, add custom domain object open class BankAccount @JvmOverloads constructor( val account: Account, val identifier: String, diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanMedium.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanMedium.kt index 3186b1d2..16f94467 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanMedium.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanMedium.kt @@ -6,6 +6,10 @@ open class TanMedium( val status: TanMediumStatus ) { + + internal constructor() : this("", TanMediumStatus.Available) // for object deserializers + + override fun toString(): String { return "$displayName $status" } diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanProcedure.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanProcedure.kt index 224868db..d213db31 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanProcedure.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/model/tan/TanProcedure.kt @@ -7,6 +7,10 @@ open class TanProcedure( val bankInternalProcedureCode: String ) { + + internal constructor() : this("", TanProcedureType.EnterTan, "") // for object deserializers + + override fun toString(): String { return "$displayName ($type, ${bankInternalProcedureCode})" } diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt index a5b110fc..997933c2 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt @@ -1,5 +1,6 @@ package net.dankito.banking.ui.presenter +import net.dankito.banking.persistence.IBankingPersistence import net.dankito.banking.ui.BankingClientCallback import net.dankito.banking.ui.IBankingClient import net.dankito.banking.ui.IBankingClientCreator @@ -21,7 +22,9 @@ import net.dankito.fints.model.BankInfo import net.dankito.utils.IThreadPool import net.dankito.utils.ThreadPool import net.dankito.utils.extensions.ofMaxLength +import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.OkHttpWebClient +import org.slf4j.LoggerFactory import java.math.BigDecimal import java.util.* import kotlin.collections.ArrayList @@ -29,19 +32,22 @@ import kotlin.collections.ArrayList open class MainWindowPresenter( protected val bankingClientCreator: IBankingClientCreator, + protected val persister: IBankingPersistence, protected val base64Service: IBase64Service, - protected val router: IRouter + protected val router: IRouter, + protected val webClient: IWebClient = OkHttpWebClient(), + protected val threadPool: IThreadPool = ThreadPool() ) { companion object { protected const val OneDayMillis = 24 * 60 * 60 * 1000 + + private val log = LoggerFactory.getLogger(MainWindowPresenter::class.java) } protected val bankFinder: BankFinder = BankFinder() - protected val threadPool: IThreadPool = ThreadPool() - protected val clientsForAccounts = mutableMapOf() @@ -68,19 +74,54 @@ open class MainWindowPresenter( } + init { + threadPool.runAsync { + readPersistedAccounts() + } + } + + + protected open fun readPersistedAccounts() { + try { + val deserializedAccounts = persister.readPersistedAccounts() + + deserializedAccounts.forEach { account -> + val bank = account.bank + val bankInfo = BankInfo(bank.name, bank.bankCode, bank.bic, "", "", "", bank.finTsServerAddress, "FinTS V3.0", null) + + val newClient = bankingClientCreator.createClient(bankInfo, account.customerId, account.password, webClient, base64Service, threadPool, callback) + + addClientForAccount(account, newClient) + } + + callAccountsChangedListeners() + + selectedAllBankAccounts() // TODO: save last selected bank account(s) + } catch (e: Exception) { + log.error("Could not deserialize persisted accounts with persister $persister", e) + } + } + + protected open fun addClientForAccount(account: Account, client: IBankingClient) { + clientsForAccounts.put(account, client) + } + + // TODO: move BankInfo out of fints4javaLib open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) { - val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, OkHttpWebClient(), base64Service, threadPool, this.callback) + val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, webClient, base64Service, threadPool, this.callback) newClient.addAccountAsync { response -> val account = response.account if (response.isSuccessful) { - clientsForAccounts.put(account, newClient) + addClientForAccount(account, newClient) callAccountsChangedListeners() + persistAccount(account) + if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) { account.bankAccounts.forEach { bankAccount -> retrievedAccountTransactions(bankAccount, response) @@ -155,6 +196,12 @@ open class MainWindowPresenter( response.balances.forEach { entry -> entry.key.balance = entry.value } + + persistAccount(bankAccount.account) + } + + protected open fun persistAccount(account: Account) { + persister.saveOrUpdateAccount(account, accounts) } diff --git a/fints4javaAndroidApp/build.gradle b/fints4javaAndroidApp/build.gradle index 18eae8f7..6f1ef512 100644 --- a/fints4javaAndroidApp/build.gradle +++ b/fints4javaAndroidApp/build.gradle @@ -48,21 +48,23 @@ android { } dependencies { - api project(':BankingUiCommon') + implementation project(':BankingUiCommon') implementation project(':fints4javaBankingClient') + implementation project(':BankingPersistenceJson') + implementation "com.github.clans:fab:$clansFloatingActionButtonVersion" - api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - api "net.dankito.utils:android-utils:$androidUtilsVersion", { + implementation "net.dankito.utils:android-utils:$androidUtilsVersion", { exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7' exclude group: 'com.android.support', module: 'appcompat-v7' exclude group: 'com.android.support', module: 'design' } - api "org.slf4j:slf4j-android:$slf4JVersion" + implementation "org.slf4j:slf4j-android:$slf4JVersion" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:design:28.0.0' diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt index 52b91765..5c47ea69 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt @@ -18,9 +18,11 @@ import kotlinx.android.synthetic.main.action_view_account_menu_item.view.* import net.dankito.banking.fints4java.android.ui.views.MainActivityFloatingActionMenuButton import net.dankito.banking.fints4java.android.util.Base64ServiceAndroid import net.dankito.banking.fints4javaBankingClientCreator +import net.dankito.banking.persistence.BankingPersistenceJson import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.presenter.MainWindowPresenter import org.slf4j.LoggerFactory +import java.io.File class MainActivity : AppCompatActivity() { @@ -37,12 +39,14 @@ class MainActivity : AppCompatActivity() { private lateinit var floatingActionMenuButton: MainActivityFloatingActionMenuButton - val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), Base64ServiceAndroid(), RouterAndroid(this)) + lateinit var presenter: MainWindowPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File(this.filesDir, "data/accounts.json")), Base64ServiceAndroid(), RouterAndroid(this)) + initUi() } diff --git a/persistence/json/BankingPersistenceJson/README.md b/persistence/json/BankingPersistenceJson/README.md new file mode 100644 index 00000000..2e62b22f --- /dev/null +++ b/persistence/json/BankingPersistenceJson/README.md @@ -0,0 +1,7 @@ +# BankingPersistenceJson + +A simple IBankingPersistence implementation based on Json. + +Not meant to be a real or useful implementation. Just there to get you up and running fast. + +Preferably use another IBankingPersistence implementation, e.g. a JPA based one. \ No newline at end of file diff --git a/persistence/json/BankingPersistenceJson/build.gradle b/persistence/json/BankingPersistenceJson/build.gradle new file mode 100644 index 00000000..4e23da0e --- /dev/null +++ b/persistence/json/BankingPersistenceJson/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' + +apply plugin: 'kotlin-kapt' + + +sourceCompatibility = "1.7" +targetCompatibility = "1.7" + +compileKotlin { + kotlinOptions.jvmTarget = "1.6" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.6" +} + + +dependencies { + implementation project(':BankingUiCommon') +} \ No newline at end of file diff --git a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt new file mode 100644 index 00000000..6fb5bf88 --- /dev/null +++ b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt @@ -0,0 +1,28 @@ +package net.dankito.banking.persistence + +import net.dankito.banking.ui.model.Account +import net.dankito.utils.serialization.ISerializer +import net.dankito.utils.serialization.JacksonJsonSerializer +import java.io.File + + +open class BankingPersistenceJson( + protected val jsonFile: File, + protected val serializer: ISerializer = JacksonJsonSerializer() +) : IBankingPersistence { + + + init { + jsonFile.absoluteFile.parentFile.mkdirs() + } + + + override fun saveOrUpdateAccount(account: Account, allAccounts: List) { + serializer.serializeObject(allAccounts, jsonFile) + } + + override fun readPersistedAccounts(): List { + return serializer.deserializeListOr(jsonFile, Account::class.java, listOf()) + } + +} \ No newline at end of file