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 b82e5131..3ffc17e7 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 @@ -8,8 +8,10 @@ import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.Menu import androidx.navigation.findNavController +import net.dankito.banking.fints4java.android.mapper.fints4javaModelMapper import net.dankito.banking.fints4java.android.ui.MainWindowPresenter import net.dankito.banking.fints4java.android.ui.dialogs.AddAccountDialog +import net.dankito.banking.fints4java.android.ui.dialogs.EnterAtcDialog import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog import net.dankito.fints.FinTsClientCallback import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium @@ -18,6 +20,7 @@ import net.dankito.fints.model.EnterTanGeneratorAtcResult import net.dankito.fints.model.TanChallenge import net.dankito.fints.model.TanProcedure import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference @@ -37,7 +40,7 @@ class MainActivity : AppCompatActivity() { } override fun enterTanGeneratorAtc(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult? { - return null + return getAtcFromUserOffUiThread(customer, tanMedium) } }) @@ -96,18 +99,34 @@ class MainActivity : AppCompatActivity() { val account = presenter.getAccountForCustomer(customer) runOnUiThread { - EnterTanDialog().show(account, tanChallenge, this@MainActivity, false) { + EnterTanDialog().show(account, tanChallenge, presenter, this@MainActivity, false) { enteredTan.set(it) tanEnteredLatch.countDown() } } - try { - tanEnteredLatch.await() - } catch (ignored: Exception) { - } + try { tanEnteredLatch.await() } catch (ignored: Exception) { } return enteredTan.get() } + private fun getAtcFromUserOffUiThread(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult? { + val enteredTan = AtomicReference(null) + val enteredAtc = AtomicInteger() + val tanEnteredLatch = CountDownLatch(1) + + runOnUiThread { + // TODO: don't create a fints4javaModelMapper instance here, let MainWindowPresenter do the job + EnterAtcDialog().show(fints4javaModelMapper().mapTanMedium(tanMedium), this@MainActivity, false) { tan, atc -> + enteredTan.set(tan) + atc?.let { enteredAtc.set(atc) } + tanEnteredLatch.countDown() + } + } + + try { tanEnteredLatch.await() } catch (ignored: Exception) { } + + return if (enteredTan.get() == null) null else EnterTanGeneratorAtcResult(enteredTan.get(), enteredAtc.get()) + } + } diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/mapper/fints4javaModelMapper.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/mapper/fints4javaModelMapper.kt index a486c2d0..ea5c2c8e 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/mapper/fints4javaModelMapper.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/mapper/fints4javaModelMapper.kt @@ -103,11 +103,16 @@ open class fints4javaModelMapper { protected open fun getDisplayNameForTanMedium(tanMedium: net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedium): String { if (tanMedium is TanGeneratorTanMedium) { - tanMedium.mediaName?.let { mediaName -> - return "$mediaName ${tanMedium.cardNumber}" + var cardNumber = tanMedium.cardNumber + tanMedium.followUpCardNumber?.let { + cardNumber += " (Folgenummer $it)" // TODO: translate } - return "Card ${tanMedium.cardNumber}" // TODO: translate + tanMedium.mediaName?.let { mediaName -> + return "$mediaName $cardNumber" + } + + return "Karte $cardNumber" // TODO: translate } return tanMedium.mediumClass.name diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/MainWindowPresenter.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/MainWindowPresenter.kt index c82f8d35..c35237ce 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/MainWindowPresenter.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/MainWindowPresenter.kt @@ -3,11 +3,13 @@ package net.dankito.banking.fints4java.android.ui import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.BankAccount +import net.dankito.banking.ui.model.TanMedium import net.dankito.banking.ui.model.responses.AddAccountResponse import net.dankito.banking.ui.model.responses.GetTransactionsResponse import net.dankito.fints.FinTsClientCallback import net.dankito.fints.FinTsClientForCustomer import net.dankito.fints.banks.BankFinder +import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium import net.dankito.fints.model.BankInfo import net.dankito.fints.model.BankTransferData import net.dankito.fints.model.CustomerData @@ -142,6 +144,16 @@ open class MainWindowPresenter(protected val base64Service: IBase64Service, } + open fun changeTanMediumAsync(newUsedTanMedium: TanMedium, account: Account, callback: (FinTsClientResponse) -> Unit) { + (newUsedTanMedium.originalObject as? TanGeneratorTanMedium)?.let { tanGeneratorTanMedium -> + getClientForAccount(account)?.changeTanMedium(tanGeneratorTanMedium, callback) + // TODO: find a way to update account.tanMedia afterwards + } + + // TODO: what to do if newActiveTanMedium.originalObject is not of type TanGeneratorTanMedium? + } + + open fun searchForBankAsync(enteredBankCode: String, callback: (List) -> Unit) { threadPool.runAsync { callback(searchForBank(enteredBankCode)) @@ -181,6 +193,11 @@ open class MainWindowPresenter(protected val base64Service: IBase64Service, } + open fun getErrorToShowToUser(response: FinTsClientResponse): String? { + return fints4javaModelMapper.mapErrorToShowToUser(response) + } + + open val allTransactions: List get() = accounts.keys.flatMap { it.transactions }.sortedByDescending { it.bookingDate } // TODO: someday add unbooked transactions diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/adapter/TanMediumAdapter.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/adapter/TanMediumAdapter.kt index 164701ae..37fa7051 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/adapter/TanMediumAdapter.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/adapter/TanMediumAdapter.kt @@ -12,12 +12,12 @@ import net.dankito.utils.android.ui.adapter.ListAdapter open class TanMediumAdapter : ListAdapter() { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? { - val procedure = getItem(position) + val tanMedium = getItem(position) val view = convertView ?: parent?.context?.asActivity()?.layoutInflater?.inflate( R.layout.list_item_tan_medium, parent, false) - view?.findViewById(R.id.txtTanMediumDisplayName)?.text = procedure.displayName + view?.findViewById(R.id.txtTanMediumDisplayName)?.text = tanMedium.displayName return view } diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/BankTransferDialog.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/BankTransferDialog.kt index 72a1811f..79a6998d 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/BankTransferDialog.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/BankTransferDialog.kt @@ -134,7 +134,7 @@ open class BankTransferDialog : DialogFragment() { else { context.getString(R.string.dialog_bank_transfer_message_transfer_failed, String.format("%.02f", transferData.amount), "€", transferData.creditorName, // TODO: where to get currency from? - response.exception ?: response.errorsToShowToUser.joinToString("\n") + presenter.getErrorToShowToUser(response) ) } diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterAtcDialog.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterAtcDialog.kt new file mode 100644 index 00000000..312d872c --- /dev/null +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterAtcDialog.kt @@ -0,0 +1,86 @@ +package net.dankito.banking.fints4java.android.ui.dialogs + +import android.os.Bundle +import android.support.v4.app.DialogFragment +import android.support.v7.app.AlertDialog +import android.support.v7.app.AppCompatActivity +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import kotlinx.android.synthetic.main.dialog_enter_atc.view.* +import net.dankito.banking.fints4java.android.R +import net.dankito.banking.ui.model.TanMedium + + +open class EnterAtcDialog : DialogFragment() { + + companion object { + const val DialogTag = "EnterAtcDialog" + } + + + protected lateinit var tanMedium: TanMedium + + protected lateinit var atcEnteredCallback: (tan: String?, atc: Int?) -> Unit + + + open fun show(tanMedium: TanMedium, activity: AppCompatActivity, + fullscreen: Boolean = false, atcEnteredCallback: (tan: String?, atc: Int?) -> Unit) { + + this.tanMedium = tanMedium + this.atcEnteredCallback = atcEnteredCallback + + val style = if(fullscreen) R.style.FullscreenDialogWithStatusBar else R.style.Dialog + setStyle(STYLE_NORMAL, style) + + show(activity.supportFragmentManager, DialogTag) + } + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val rootView = inflater.inflate(R.layout.dialog_enter_atc, container, false) + + setupUI(rootView) + + return rootView + } + + protected open fun setupUI(rootView: View) { + val explanationHtml = rootView.context.getString(R.string.dialog_enter_atc_explanation, tanMedium.displayName) + rootView.txtAtcExplanationToShowToUser.text = Html.fromHtml(explanationHtml, Html.FROM_HTML_MODE_LEGACY) + + rootView.btnCancel.setOnClickListener { enteringAtcDone(null, null) } + + rootView.btnEnteringAtcDone.setOnClickListener { enteringAtcDone(rootView.edtxtEnteredTan.text.toString(), rootView.edtxtEnteredAtc.text.toString()) } + } + + + protected open fun enteringAtcDone(enteredTan: String?, enteredAtcString: String?) { + var enteredAtc: Int? = null + + if (enteredAtcString != null) { + try { + enteredAtc = enteredAtcString.toInt() + } catch (e: Exception) { + showEnteredAtcIsNotANumberError(enteredAtcString) + + return + } + } + + atcEnteredCallback(enteredTan, enteredAtc) + + dismiss() + } + + protected open fun showEnteredAtcIsNotANumberError(enteredAtcString: String) { + activity?.let { context -> + AlertDialog.Builder(context) + .setMessage(context.getString(R.string.dialog_enter_atc_error_entered_atc_is_not_a_number, enteredAtcString)) + .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() + } + } + +} \ No newline at end of file diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterTanDialog.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterTanDialog.kt index 1ed29ac1..5e7043e5 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterTanDialog.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/dialogs/EnterTanDialog.kt @@ -1,7 +1,9 @@ package net.dankito.banking.fints4java.android.ui.dialogs +import android.content.Context import android.os.Bundle import android.support.v4.app.DialogFragment +import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity import android.text.InputType import android.view.LayoutInflater @@ -9,11 +11,15 @@ import android.view.View import android.view.ViewGroup import kotlinx.android.synthetic.main.dialog_enter_tan.view.* import net.dankito.banking.fints4java.android.R +import net.dankito.banking.fints4java.android.ui.MainWindowPresenter import net.dankito.banking.fints4java.android.ui.adapter.TanMediumAdapter +import net.dankito.banking.fints4java.android.ui.listener.ListItemSelectedListener import net.dankito.banking.ui.model.Account +import net.dankito.banking.ui.model.TanMedium import net.dankito.banking.ui.model.TanMediumStatus import net.dankito.fints.model.TanChallenge import net.dankito.fints.model.TanProcedureType +import net.dankito.fints.response.client.FinTsClientResponse import net.dankito.fints.tan.FlickercodeDecoder @@ -28,16 +34,19 @@ open class EnterTanDialog : DialogFragment() { protected lateinit var tanChallenge: TanChallenge + protected lateinit var presenter: MainWindowPresenter + protected lateinit var tanEnteredCallback: (String?) -> Unit protected val tanMediumAdapter = TanMediumAdapter() - open fun show(account: Account, tanChallenge: TanChallenge, activity: AppCompatActivity, + open fun show(account: Account, tanChallenge: TanChallenge, presenter: MainWindowPresenter, activity: AppCompatActivity, fullscreen: Boolean = false, tanEnteredCallback: (String?) -> Unit) { this.account = account this.tanChallenge = tanChallenge + this.presenter = presenter this.tanEnteredCallback = tanEnteredCallback val style = if(fullscreen) R.style.FullscreenDialogWithStatusBar else R.style.Dialog @@ -60,9 +69,7 @@ open class EnterTanDialog : DialogFragment() { if (tanChallenge.tanProcedure.type == TanProcedureType.ChipTanOptisch) { if (account.tanMedia.isNotEmpty()) { - rootView.lytTanMedium.visibility = View.VISIBLE - tanMediumAdapter.setItems(account.tanMedia.sortedByDescending { it.status == TanMediumStatus.Used }) - rootView.spnTanMedium.adapter = tanMediumAdapter + setupSelectTanMediumView(rootView) } flickerCodeView.visibility = View.VISIBLE @@ -78,6 +85,48 @@ open class EnterTanDialog : DialogFragment() { rootView.btnEnteringTanDone.setOnClickListener { enteringTanDone(rootView.edtxtEnteredTan.text.toString()) } } + protected open fun setupSelectTanMediumView(rootView: View) { + rootView.lytTanMedium.visibility = View.VISIBLE + + tanMediumAdapter.setItems(account.tanMedia.sortedByDescending { it.status == TanMediumStatus.Used }) + + rootView.spnTanMedium.adapter = tanMediumAdapter + rootView.spnTanMedium.onItemSelectedListener = ListItemSelectedListener(tanMediumAdapter) { selectedTanMedium -> + if (selectedTanMedium.status != TanMediumStatus.Used) { + presenter.changeTanMediumAsync(selectedTanMedium, account) { response -> + handleChangeTanMediumResponse(selectedTanMedium, response) + } + } + } + } + + private fun handleChangeTanMediumResponse(newUsedTanMedium: TanMedium, response: FinTsClientResponse) { + activity?.let { activity -> + activity.runOnUiThread { + handleChangeTanMediumResponseOnUiThread(activity, newUsedTanMedium, response) + } + } + } + + + protected open fun handleChangeTanMediumResponseOnUiThread(context: Context, newUsedTanMedium: TanMedium, response: FinTsClientResponse) { + if (response.isSuccessful) { + dismiss() + + AlertDialog.Builder(context) + .setMessage(context.getString(R.string.dialog_enter_tan_tan_medium_successfully_changed, newUsedTanMedium.displayName)) + .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() + } + else { + AlertDialog.Builder(context) + .setMessage(context.getString(R.string.dialog_enter_tan_error_changing_tan_medium, newUsedTanMedium.displayName, presenter.getErrorToShowToUser(response))) + .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() + } + } + + protected open fun enteringTanDone(enteredTan: String?) { tanEnteredCallback(enteredTan) diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/listener/ItemSelectedListener.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/listener/ItemSelectedListener.kt new file mode 100644 index 00000000..738a923a --- /dev/null +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/listener/ItemSelectedListener.kt @@ -0,0 +1,15 @@ +package net.dankito.banking.fints4java.android.ui.listener + +import android.view.View +import android.widget.AdapterView + + +open class ItemSelectedListener(val itemSelected: (position: Int) -> Unit) : AdapterView.OnItemSelectedListener { + + override fun onNothingSelected(parent: AdapterView<*>?) { } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + itemSelected(position) + } + +} \ No newline at end of file diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/listener/ListItemSelectedListener.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/listener/ListItemSelectedListener.kt new file mode 100644 index 00000000..756368ca --- /dev/null +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/ui/listener/ListItemSelectedListener.kt @@ -0,0 +1,16 @@ +package net.dankito.banking.fints4java.android.ui.listener + +import android.view.View +import android.widget.AdapterView +import net.dankito.utils.android.ui.adapter.ListAdapter + + +open class ListItemSelectedListener(val adapter: ListAdapter, val itemSelected: (item: T) -> Unit) : AdapterView.OnItemSelectedListener { + + override fun onNothingSelected(parent: AdapterView<*>?) { } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + itemSelected(adapter.getItem(position)) + } + +} \ No newline at end of file diff --git a/fints4javaAndroidApp/src/main/res/layout/dialog_enter_atc.xml b/fints4javaAndroidApp/src/main/res/layout/dialog_enter_atc.xml new file mode 100644 index 00000000..f2ffe60c --- /dev/null +++ b/fints4javaAndroidApp/src/main/res/layout/dialog_enter_atc.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +