Implemented parsing and displaying images for chipTan-QR and PhotoTan; extracted TanImageSizeControlView

This commit is contained in:
dankl 2020-01-02 00:02:35 +01:00 committed by dankito
parent 0660498ceb
commit aa88c751dd
12 changed files with 270 additions and 32 deletions

View File

@ -1,6 +1,7 @@
package net.dankito.banking.fints4java.android.ui.dialogs
import android.content.Context
import android.graphics.BitmapFactory
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
@ -11,6 +12,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Spinner
import kotlinx.android.synthetic.main.dialog_enter_tan.view.*
import kotlinx.android.synthetic.main.view_tan_image.view.*
import net.dankito.banking.fints4java.android.R
import net.dankito.banking.fints4java.android.mapper.fints4javaModelMapper
import net.dankito.banking.fints4java.android.ui.MainWindowPresenter
@ -26,11 +28,14 @@ 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
import net.dankito.fints.tan.TanImageDecoder
open class EnterTanDialog : DialogFragment() {
companion object {
val OpticalTanProcedures = listOf(TanProcedureType.ChipTanOptisch, TanProcedureType.ChipTanQrCode, TanProcedureType.PhotoTan)
const val DialogTag = "EnterTanDialog"
}
@ -70,17 +75,28 @@ open class EnterTanDialog : DialogFragment() {
}
protected open fun setupUI(rootView: View) {
val flickerCodeView = rootView.flickerCodeView
setupSelectTanProcedureView(rootView)
if (tanChallenge.tanProcedure.type == TanProcedureType.ChipTanOptisch) {
if (OpticalTanProcedures.contains(tanChallenge.tanProcedure.type)) {
if (account.tanMedia.isNotEmpty()) {
setupSelectTanMediumView(rootView)
}
flickerCodeView.visibility = View.VISIBLE
flickerCodeView.setCode(FlickercodeDecoder().decodeChallenge(tanChallenge.tanChallenge))
if (tanChallenge.tanProcedure.type == TanProcedureType.ChipTanOptisch) {
val flickerCodeView = rootView.flickerCodeView
flickerCodeView.visibility = View.VISIBLE
flickerCodeView.setCode(FlickercodeDecoder().decodeChallenge(tanChallenge.tanChallenge))
}
else if (tanChallenge.tanProcedure.type == TanProcedureType.ChipTanQrCode
|| tanChallenge.tanProcedure.type == TanProcedureType.PhotoTan) {
rootView.tanImageView.visibility = View.VISIBLE
TanImageDecoder().decodeChallenge(tanChallenge.tanChallenge)?.let { tanImage ->
val bitmap = BitmapFactory.decodeByteArray(tanImage.imageBytes, 0, tanImage.imageBytes.size)
rootView.imgTanImageView.setImageBitmap(bitmap)
}
// TODO: what to do if decoding fails? At least a message should get shown to user
}
rootView.edtxtEnteredTan.inputType = InputType.TYPE_CLASS_NUMBER
}

View File

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_flicker_code.view.*
import kotlinx.android.synthetic.main.view_tan_image_size_controls.view.*
import net.dankito.banking.fints4java.android.R
import net.dankito.banking.fints4java.android.util.FlickercodeAnimator
import net.dankito.fints.tan.Bit

View File

@ -0,0 +1,24 @@
package net.dankito.banking.fints4java.android.ui.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import net.dankito.banking.fints4java.android.R
open class TanImageSizeControlsView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
init {
setupUi(context)
}
private fun setupUi(context: Context) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.view_tan_image_size_controls, this, true)
}
}

View File

@ -0,0 +1,80 @@
package net.dankito.banking.fints4java.android.ui.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_tan_image.view.*
import kotlinx.android.synthetic.main.view_tan_image_size_controls.view.*
import net.dankito.banking.fints4java.android.R
open class TanImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
companion object {
const val ChangeSizeStepSizeDp = 10f
}
protected val MinHeight: Int
protected val MaxHeight: Int
protected lateinit var imgTanImageView: ImageView
init {
MinHeight = convertDpToPx(context, 150f).toInt()
MaxHeight = convertDpToPx(context, 500f).toInt()
setupUi(context)
}
private fun setupUi(context: Context) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val rootView = inflater.inflate(R.layout.view_tan_image, this, true)
imgTanImageView = rootView.imgTanImageView
rootView.btnIncreaseSize.setOnClickListener { increaseSize() }
rootView.btnDecreaseSize.setOnClickListener { decreaseSize() }
}
open fun increaseSize() {
changeSizeBy(ChangeSizeStepSizeDp)
}
open fun decreaseSize() {
changeSizeBy(ChangeSizeStepSizeDp * -1)
}
protected open fun changeSizeBy(changeSizeBy: Float) {
val params = imgTanImageView.layoutParams
val newWidthAndHeight = params.height + convertDpToPx(context, changeSizeBy).toInt()
if (newWidthAndHeight in MinHeight..MaxHeight) {
params.height = newWidthAndHeight
params.width = newWidthAndHeight
imgTanImageView.layoutParams = params // TODO: needed?
requestLayout()
}
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*
* @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
* @param context Context to get resources and device specific display metrics
* @return A float value to represent px equivalent to dp depending on device density
*/
open fun convertDpToPx(context: Context, dp: Float): Float {
return dp * context.resources.displayMetrics.density
}
}

View File

@ -69,6 +69,15 @@
android:visibility="gone"
/>
<net.dankito.banking.fints4java.android.ui.views.TanImageView
android:id="@+id/tanImageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:visibility="gone"
/>
<TextView
android:id="@+id/txtTanDescriptionToShowToUser"

View File

@ -9,31 +9,17 @@
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
>
<TextView
<net.dankito.banking.fints4java.android.ui.views.TanImageSizeControlsView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/view_flicker_code_size"
/>
<Button
android:id="@+id/btnIncreaseSize"
android:layout_width="@dimen/view_flicker_code_control_buttons_width"
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
android:text="@string/view_flicker_code_increase_size"
/>
<Button
android:id="@+id/btnDecreaseSize"
android:layout_width="@dimen/view_flicker_code_control_buttons_width"
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
android:text="@string/view_flicker_code_decrease_size"
/>
android:layout_marginRight="@dimen/view_flicker_code_controls_sections_space"
android:layout_marginEnd="@dimen/view_flicker_code_controls_sections_space"/>
<TextView
android:layout_width="wrap_content"
@ -44,15 +30,15 @@
<Button
android:id="@+id/btnIncreaseSpeed"
android:layout_width="@dimen/view_flicker_code_control_buttons_width"
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
android:layout_width="@dimen/view_tan_image_controls_buttons_width"
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
android:text="@string/view_flicker_code_increase_frequency"
/>
<Button
android:id="@+id/btnDecreaseSpeed"
android:layout_width="@dimen/view_flicker_code_control_buttons_width"
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
android:layout_width="@dimen/view_tan_image_controls_buttons_width"
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
android:text="@string/view_flicker_code_decrease_frequency"
/>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
>
<net.dankito.banking.fints4java.android.ui.views.TanImageSizeControlsView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
/>
<ImageView
android:id="@+id/imgTanImageView"
android:layout_width="match_parent"
android:layout_height="@dimen/view_tan_image_image_height"
android:layout_gravity="center"
android:paddingTop="@dimen/view_tan_image_image_margin_top_bottom"
android:paddingBottom="@dimen/view_tan_image_image_margin_top_bottom"
/>
</LinearLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/view_tan_image_size_controls_size"
/>
<Button
android:id="@+id/btnIncreaseSize"
android:layout_width="@dimen/view_tan_image_controls_buttons_width"
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
android:text="@string/view_tan_image_size_controls_increase_size"
/>
<Button
android:id="@+id/btnDecreaseSize"
android:layout_width="@dimen/view_tan_image_controls_buttons_width"
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
android:text="@string/view_tan_image_size_controls_decrease_size"
/>
</LinearLayout>

View File

@ -39,8 +39,13 @@
<dimen name="dialog_bank_transfer_autocomplete_fields_height">50dp</dimen>
<dimen name="dialog_bank_transfer_buttons_width">120dp</dimen>
<dimen name="view_flicker_code_control_buttons_height">40dp</dimen>
<dimen name="view_flicker_code_control_buttons_width">40dp</dimen>
<dimen name="view_tan_image_controls_buttons_height">40dp</dimen>
<dimen name="view_tan_image_controls_buttons_width">40dp</dimen>
<dimen name="view_tan_image_image_height">200dp</dimen>
<dimen name="view_tan_image_image_margin_top_bottom">4dp</dimen>
<dimen name="view_flicker_code_controls_sections_space">12dp</dimen>
<dimen name="view_flicker_code_stripe_height">120dp</dimen>
<dimen name="view_flicker_code_stripe_width">40dp</dimen>
<dimen name="view_flicker_code_stripe_margin_right">10dp</dimen>

View File

@ -50,9 +50,10 @@
<string name="dialog_bank_transfer_message_transfer_successful">Successfully transferred %1$s %2$s to %3$s.</string>
<string name="dialog_bank_transfer_message_transfer_failed">Could not transfer %1$s %2$s to %3$s:\n%4$s.</string>
<string name="view_flicker_code_size">Size:</string>
<string name="view_flicker_code_increase_size">+</string>
<string name="view_flicker_code_decrease_size">-</string>
<string name="view_tan_image_size_controls_size">Size:</string>
<string name="view_tan_image_size_controls_increase_size">+</string>
<string name="view_tan_image_size_controls_decrease_size">-</string>
<string name="view_flicker_code_frequency">Frequency:</string>
<string name="view_flicker_code_increase_frequency">+</string>
<string name="view_flicker_code_decrease_frequency">-</string>

View File

@ -0,0 +1,13 @@
package net.dankito.fints.tan
open class TanImage(
val mimeType: String,
val imageBytes: ByteArray
) {
override fun toString(): String {
return "$mimeType ${imageBytes.size} bytes"
}
}

View File

@ -0,0 +1,45 @@
package net.dankito.fints.tan
import net.dankito.fints.messages.HbciCharset
import org.slf4j.LoggerFactory
open class TanImageDecoder {
companion object {
private val log = LoggerFactory.getLogger(TanImageDecoder::class.java)
}
open fun decodeChallenge(challengeHHD_UC: String): TanImage? {
try {
val bytes = challengeHHD_UC.toByteArray(HbciCharset.DefaultCharset)
val mimeTypeLength = getLength(bytes[0], bytes[1])
val mimeTypeEnd = 2 + mimeTypeLength
val mimeType = challengeHHD_UC.substring(2, mimeTypeEnd)
val imageLength = getLength(bytes[mimeTypeEnd], bytes[mimeTypeEnd + 1])
val imageStart = mimeTypeEnd + 2
val imageString = challengeHHD_UC.substring(imageStart, imageStart + imageLength)
val imageBytes = imageString.toByteArray(HbciCharset.DefaultCharset)
return TanImage(mimeType, imageBytes)
} catch (e: Exception) {
log.error("Could not decode challenge HHD_UC to TanImage: $challengeHHD_UC", e)
}
return null
}
protected open fun getLength(higherOrderByte: Byte, lowerOrderByte: Byte): Int {
return 256 * byteToUnsignedInt(higherOrderByte) + byteToUnsignedInt(lowerOrderByte)
}
protected open fun byteToUnsignedInt(byte: Byte): Int {
return byte.toUByte().toInt()
}
}