Implemented parsing and displaying images for chipTan-QR and PhotoTan; extracted TanImageSizeControlView
This commit is contained in:
parent
0660498ceb
commit
aa88c751dd
|
@ -1,6 +1,7 @@
|
||||||
package net.dankito.banking.fints4java.android.ui.dialogs
|
package net.dankito.banking.fints4java.android.ui.dialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.DialogFragment
|
import android.support.v4.app.DialogFragment
|
||||||
import android.support.v7.app.AlertDialog
|
import android.support.v7.app.AlertDialog
|
||||||
|
@ -11,6 +12,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import kotlinx.android.synthetic.main.dialog_enter_tan.view.*
|
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.R
|
||||||
import net.dankito.banking.fints4java.android.mapper.fints4javaModelMapper
|
import net.dankito.banking.fints4java.android.mapper.fints4javaModelMapper
|
||||||
import net.dankito.banking.fints4java.android.ui.MainWindowPresenter
|
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.model.TanProcedureType
|
||||||
import net.dankito.fints.response.client.FinTsClientResponse
|
import net.dankito.fints.response.client.FinTsClientResponse
|
||||||
import net.dankito.fints.tan.FlickercodeDecoder
|
import net.dankito.fints.tan.FlickercodeDecoder
|
||||||
|
import net.dankito.fints.tan.TanImageDecoder
|
||||||
|
|
||||||
|
|
||||||
open class EnterTanDialog : DialogFragment() {
|
open class EnterTanDialog : DialogFragment() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val OpticalTanProcedures = listOf(TanProcedureType.ChipTanOptisch, TanProcedureType.ChipTanQrCode, TanProcedureType.PhotoTan)
|
||||||
|
|
||||||
const val DialogTag = "EnterTanDialog"
|
const val DialogTag = "EnterTanDialog"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,17 +75,28 @@ open class EnterTanDialog : DialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun setupUI(rootView: View) {
|
protected open fun setupUI(rootView: View) {
|
||||||
val flickerCodeView = rootView.flickerCodeView
|
|
||||||
|
|
||||||
setupSelectTanProcedureView(rootView)
|
setupSelectTanProcedureView(rootView)
|
||||||
|
|
||||||
if (tanChallenge.tanProcedure.type == TanProcedureType.ChipTanOptisch) {
|
if (OpticalTanProcedures.contains(tanChallenge.tanProcedure.type)) {
|
||||||
if (account.tanMedia.isNotEmpty()) {
|
if (account.tanMedia.isNotEmpty()) {
|
||||||
setupSelectTanMediumView(rootView)
|
setupSelectTanMediumView(rootView)
|
||||||
}
|
}
|
||||||
|
|
||||||
flickerCodeView.visibility = View.VISIBLE
|
if (tanChallenge.tanProcedure.type == TanProcedureType.ChipTanOptisch) {
|
||||||
flickerCodeView.setCode(FlickercodeDecoder().decodeChallenge(tanChallenge.tanChallenge))
|
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
|
rootView.edtxtEnteredTan.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import kotlinx.android.synthetic.main.view_flicker_code.view.*
|
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.R
|
||||||
import net.dankito.banking.fints4java.android.util.FlickercodeAnimator
|
import net.dankito.banking.fints4java.android.util.FlickercodeAnimator
|
||||||
import net.dankito.fints.tan.Bit
|
import net.dankito.fints.tan.Bit
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -69,6 +69,15 @@
|
||||||
android:visibility="gone"
|
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
|
<TextView
|
||||||
android:id="@+id/txtTanDescriptionToShowToUser"
|
android:id="@+id/txtTanDescriptionToShowToUser"
|
||||||
|
|
|
@ -9,31 +9,17 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
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:layout_gravity="center_horizontal"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
>
|
>
|
||||||
|
|
||||||
<TextView
|
<net.dankito.banking.fints4java.android.ui.views.TanImageSizeControlsView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:text="@string/view_flicker_code_size"
|
android:layout_marginRight="@dimen/view_flicker_code_controls_sections_space"
|
||||||
/>
|
android:layout_marginEnd="@dimen/view_flicker_code_controls_sections_space"/>
|
||||||
|
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -44,15 +30,15 @@
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnIncreaseSpeed"
|
android:id="@+id/btnIncreaseSpeed"
|
||||||
android:layout_width="@dimen/view_flicker_code_control_buttons_width"
|
android:layout_width="@dimen/view_tan_image_controls_buttons_width"
|
||||||
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
|
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
|
||||||
android:text="@string/view_flicker_code_increase_frequency"
|
android:text="@string/view_flicker_code_increase_frequency"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnDecreaseSpeed"
|
android:id="@+id/btnDecreaseSpeed"
|
||||||
android:layout_width="@dimen/view_flicker_code_control_buttons_width"
|
android:layout_width="@dimen/view_tan_image_controls_buttons_width"
|
||||||
android:layout_height="@dimen/view_flicker_code_control_buttons_height"
|
android:layout_height="@dimen/view_tan_image_controls_buttons_height"
|
||||||
android:text="@string/view_flicker_code_decrease_frequency"
|
android:text="@string/view_flicker_code_decrease_frequency"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -39,8 +39,13 @@
|
||||||
<dimen name="dialog_bank_transfer_autocomplete_fields_height">50dp</dimen>
|
<dimen name="dialog_bank_transfer_autocomplete_fields_height">50dp</dimen>
|
||||||
<dimen name="dialog_bank_transfer_buttons_width">120dp</dimen>
|
<dimen name="dialog_bank_transfer_buttons_width">120dp</dimen>
|
||||||
|
|
||||||
<dimen name="view_flicker_code_control_buttons_height">40dp</dimen>
|
<dimen name="view_tan_image_controls_buttons_height">40dp</dimen>
|
||||||
<dimen name="view_flicker_code_control_buttons_width">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_height">120dp</dimen>
|
||||||
<dimen name="view_flicker_code_stripe_width">40dp</dimen>
|
<dimen name="view_flicker_code_stripe_width">40dp</dimen>
|
||||||
<dimen name="view_flicker_code_stripe_margin_right">10dp</dimen>
|
<dimen name="view_flicker_code_stripe_margin_right">10dp</dimen>
|
||||||
|
|
|
@ -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_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="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_tan_image_size_controls_size">Size:</string>
|
||||||
<string name="view_flicker_code_increase_size">+</string>
|
<string name="view_tan_image_size_controls_increase_size">+</string>
|
||||||
<string name="view_flicker_code_decrease_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_frequency">Frequency:</string>
|
||||||
<string name="view_flicker_code_increase_frequency">+</string>
|
<string name="view_flicker_code_increase_frequency">+</string>
|
||||||
<string name="view_flicker_code_decrease_frequency">-</string>
|
<string name="view_flicker_code_decrease_frequency">-</string>
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue