Implemented displaying FlickerCodes
This commit is contained in:
parent
4cdc573364
commit
ebbdd56418
|
@ -63,7 +63,7 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
|
||||||
fun EnterTanDialogPreview_Flickercode() {
|
fun EnterTanDialogPreview_Flickercode() {
|
||||||
val tanMethods = listOf(TanMethod("chipTAN Flickercode", TanMethodType.ChipTanFlickercode, "902"))
|
val tanMethods = listOf(TanMethod("chipTAN Flickercode", TanMethodType.ChipTanFlickercode, "902"))
|
||||||
val bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
|
val bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
|
||||||
val tanChallenge = TanChallenge(TanChallengeType.Flickercode, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank, flickerCode = FlickerCode("", ""))
|
val tanChallenge = TanChallenge(TanChallengeType.Flickercode, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank, flickerCode = FlickerCode("100880077104", "0604800771040F"))
|
||||||
|
|
||||||
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
|
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
|
||||||
}
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package net.codinux.banking.ui.composables.tan
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Fill
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import net.codinux.banking.ui.service.tan.Bit
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChipTanFlickerCodeStripeView(stripe: Bit, width: Dp, showTanGeneratorMarker: Boolean = false) {
|
||||||
|
Column(Modifier.width(width).fillMaxHeight()) {
|
||||||
|
val markerHeight = width * 0.5f
|
||||||
|
val triangleSize = markerHeight.value * LocalDensity.current.density
|
||||||
|
|
||||||
|
Column(Modifier.padding(bottom = 4.dp).width(width).height(markerHeight), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Bottom) {
|
||||||
|
if (showTanGeneratorMarker) {
|
||||||
|
val path = Path().apply {
|
||||||
|
// Line to the top-right corner of the triangle
|
||||||
|
lineTo(triangleSize, 0f)
|
||||||
|
|
||||||
|
// Line to the bottom-center point of the triangle
|
||||||
|
lineTo(triangleSize / 2, triangleSize)
|
||||||
|
|
||||||
|
// Close the path (line back to the starting point)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas(modifier = Modifier.size(markerHeight)) {
|
||||||
|
drawPath(path, Color.White, 1f, Fill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxSize().background(if (stripe.isHigh) Color.White else Color.Black)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package net.codinux.banking.ui.composables.tan
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Pause
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import net.codinux.banking.client.model.tan.FlickerCode
|
||||||
|
import net.codinux.banking.ui.service.tan.Bit
|
||||||
|
import net.codinux.banking.ui.service.tan.FlickerCodeAnimator
|
||||||
|
import net.codinux.banking.ui.service.tan.Step
|
||||||
|
|
||||||
|
|
||||||
|
private const val FrequencyStepSize = 2
|
||||||
|
|
||||||
|
private val StripesHeightStepSize = 7.dp
|
||||||
|
private val StripesWidthStepSize = 2.dp
|
||||||
|
private val SpaceBetweenStripesStepSize = 1.dp
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChipTanFlickerCodeView(flickerCode: FlickerCode) {
|
||||||
|
|
||||||
|
val animator = remember { FlickerCodeAnimator() }
|
||||||
|
|
||||||
|
var stripesHeight by remember { mutableStateOf(240.dp) }
|
||||||
|
|
||||||
|
var stripesWidth by remember { mutableStateOf(45.dp) }
|
||||||
|
|
||||||
|
var spaceBetweenStripes by remember { mutableStateOf(15.dp) }
|
||||||
|
|
||||||
|
var frequency by remember { mutableStateOf(FlickerCodeAnimator.DefaultFrequency) }
|
||||||
|
|
||||||
|
var isPaused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var step by remember { mutableStateOf(Step(Bit.High, Bit.High, Bit.High, Bit.High, Bit.High)) }
|
||||||
|
|
||||||
|
|
||||||
|
fun setSize(width: Dp, height: Dp, spaceBetween: Dp) {
|
||||||
|
stripesWidth = width
|
||||||
|
stripesHeight = height
|
||||||
|
spaceBetweenStripes = spaceBetween
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decreaseSize() {
|
||||||
|
if (spaceBetweenStripes - SpaceBetweenStripesStepSize > 0.dp) {
|
||||||
|
setSize(
|
||||||
|
stripesWidth - StripesWidthStepSize,
|
||||||
|
stripesHeight - StripesHeightStepSize,
|
||||||
|
spaceBetweenStripes - SpaceBetweenStripesStepSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun increaseSize() { // set also an upper limit to size?
|
||||||
|
setSize(
|
||||||
|
stripesWidth + StripesWidthStepSize,
|
||||||
|
stripesHeight + StripesHeightStepSize,
|
||||||
|
spaceBetweenStripes + SpaceBetweenStripesStepSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleIsPaused() {
|
||||||
|
isPaused = !isPaused
|
||||||
|
|
||||||
|
if (isPaused) {
|
||||||
|
animator.pause()
|
||||||
|
} else {
|
||||||
|
animator.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
ImageSizeControls(true, true, { decreaseSize() }, { increaseSize() })
|
||||||
|
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
|
|
||||||
|
IconButton({ toggleIsPaused() }) {
|
||||||
|
if (isPaused) {
|
||||||
|
Icon(Icons.Filled.PlayArrow, "FlickerCode Animation wieder starten")
|
||||||
|
} else {
|
||||||
|
Icon(Icons.Filled.Pause, "FlickerCode Animation pausieren")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(Modifier.background(Color.Black).padding(vertical = 20.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Row(Modifier.fillMaxWidth().height(stripesHeight).background(Color.Black), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
ChipTanFlickerCodeStripeView(step.bit1, stripesWidth, true)
|
||||||
|
|
||||||
|
Spacer(Modifier.width(spaceBetweenStripes))
|
||||||
|
|
||||||
|
ChipTanFlickerCodeStripeView(step.bit2, stripesWidth)
|
||||||
|
|
||||||
|
Spacer(Modifier.width(spaceBetweenStripes))
|
||||||
|
|
||||||
|
ChipTanFlickerCodeStripeView(step.bit3, stripesWidth)
|
||||||
|
|
||||||
|
Spacer(Modifier.width(spaceBetweenStripes))
|
||||||
|
|
||||||
|
ChipTanFlickerCodeStripeView(step.bit4, stripesWidth)
|
||||||
|
|
||||||
|
Spacer(Modifier.width(spaceBetweenStripes))
|
||||||
|
|
||||||
|
ChipTanFlickerCodeStripeView(step.bit5, stripesWidth, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DisposableEffect(animator) {
|
||||||
|
animator.setFrequency(frequency)
|
||||||
|
|
||||||
|
animator.animateFlickerCode(flickerCode) {
|
||||||
|
step = it
|
||||||
|
}
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
animator.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package net.codinux.banking.ui.composables.tan
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ZoomIn
|
||||||
|
import androidx.compose.material.icons.filled.ZoomOut
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageSizeControls(decreaseEnabled: Boolean, increaseEnabled: Boolean, onDecreaseImageSize: () -> Unit, onIncreaseImageSize: () -> Unit) {
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text("Größe")
|
||||||
|
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
|
||||||
|
TextButton({ onDecreaseImageSize() }, enabled = decreaseEnabled, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
|
||||||
|
Icon(Icons.Filled.ZoomOut, contentDescription = "Bild verkleiner", Modifier.size(28.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
|
||||||
|
TextButton({ onIncreaseImageSize() }, enabled = increaseEnabled, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
|
||||||
|
Icon(Icons.Filled.ZoomIn, contentDescription = "Bild vergrößern", Modifier.size(28.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,11 @@ import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ZoomIn
|
|
||||||
import androidx.compose.material.icons.filled.ZoomOut
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
@ -20,6 +16,8 @@ import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toLocalDateTime
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import net.codinux.banking.client.model.tan.*
|
import net.codinux.banking.client.model.tan.*
|
||||||
import net.codinux.banking.ui.composables.BankIcon
|
import net.codinux.banking.ui.composables.BankIcon
|
||||||
|
import net.codinux.banking.ui.composables.tan.ChipTanFlickerCodeView
|
||||||
|
import net.codinux.banking.ui.composables.tan.ImageSizeControls
|
||||||
import net.codinux.banking.ui.config.Colors
|
import net.codinux.banking.ui.config.Colors
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
import net.codinux.banking.ui.config.Internationalization
|
import net.codinux.banking.ui.config.Internationalization
|
||||||
|
@ -29,7 +27,6 @@ import net.codinux.banking.ui.forms.Select
|
||||||
import net.codinux.banking.ui.model.TanChallengeReceived
|
import net.codinux.banking.ui.model.TanChallengeReceived
|
||||||
import net.codinux.banking.ui.model.error.ErroneousAction
|
import net.codinux.banking.ui.model.error.ErroneousAction
|
||||||
import net.codinux.banking.ui.service.createImageBitmap
|
import net.codinux.banking.ui.service.createImageBitmap
|
||||||
import net.codinux.log.Log
|
|
||||||
import kotlin.io.encoding.Base64
|
import kotlin.io.encoding.Base64
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
|
@ -114,19 +111,9 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
|
||||||
"TAN Verfahren",
|
"TAN Verfahren",
|
||||||
challenge.availableTanMethods.sortedBy { it.identifier },
|
challenge.availableTanMethods.sortedBy { it.identifier },
|
||||||
challenge.selectedTanMethod,
|
challenge.selectedTanMethod,
|
||||||
{ tanMethod ->
|
{ tanMethod -> tanChallengeReceived.callback(EnterTanResult(null, tanMethod)) },
|
||||||
if (tanMethod.type != TanMethodType.ChipTanFlickercode) {
|
|
||||||
tanChallengeReceived.callback(EnterTanResult(null, tanMethod))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ it.displayName }
|
{ it.displayName }
|
||||||
) { tanMethod ->
|
) { tanMethod -> Text(tanMethod.displayName) }
|
||||||
if (tanMethod.type == TanMethodType.ChipTanFlickercode) {
|
|
||||||
Text(tanMethod.displayName + " (noch nicht implementiert)", color = MaterialTheme.colors.onSurface.copy(ContentAlpha.disabled))
|
|
||||||
} else {
|
|
||||||
Text(tanMethod.displayName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (challenge.availableTanMedia.isNotEmpty()) {
|
if (challenge.availableTanMedia.isNotEmpty()) {
|
||||||
|
@ -148,9 +135,8 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
|
||||||
|
|
||||||
if (challenge.tanImage != null || challenge.flickerCode != null) {
|
if (challenge.tanImage != null || challenge.flickerCode != null) {
|
||||||
Column(Modifier.fillMaxWidth().padding(top = 6.dp)) {
|
Column(Modifier.fillMaxWidth().padding(top = 6.dp)) {
|
||||||
if (challenge.flickerCode != null) {
|
challenge.flickerCode?.let { flickerCode ->
|
||||||
Text("Es tut uns Leid, für die TAN müsste ein Flickercode angezeigt werden, was wir noch nicht implementiert haben.")
|
ChipTanFlickerCodeView(flickerCode)
|
||||||
Text("Bitte wählen Sie ein anderes TAN Verfahren, z. B. chipTAN-QrCode oder manuelle TAN Eingabe wie chipTAN manuell.", Modifier.padding(top = 6.dp))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
challenge.tanImage?.let { tanImage ->
|
challenge.tanImage?.let { tanImage ->
|
||||||
|
@ -158,15 +144,7 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
|
||||||
val imageBytes = Base64.decode(tanImage.imageBytesBase64)
|
val imageBytes = Base64.decode(tanImage.imageBytesBase64)
|
||||||
|
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text("Größe")
|
ImageSizeControls(tanImageHeight > minTanImageHeight, tanImageHeight < maxTanImageHeight, { tanImageHeight -= 25 }) { tanImageHeight += 25 }
|
||||||
Spacer(Modifier.width(6.dp))
|
|
||||||
TextButton({ tanImageHeight -= 25}, enabled = tanImageHeight > minTanImageHeight, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
|
|
||||||
Icon(Icons.Filled.ZoomOut, contentDescription = "Bild mit enkodierter TAN verkleiner", Modifier.size(28.dp))
|
|
||||||
}
|
|
||||||
Spacer(Modifier.width(6.dp))
|
|
||||||
TextButton({ tanImageHeight += 25}, enabled = tanImageHeight < maxTanImageHeight, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
|
|
||||||
Icon(Icons.Filled.ZoomIn, contentDescription = "Bild mit enkodierter TAN vergrößern", Modifier.size(28.dp))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package net.codinux.banking.ui.service.tan
|
||||||
|
|
||||||
|
enum class Bit(val value: Int) {
|
||||||
|
|
||||||
|
Low(0),
|
||||||
|
|
||||||
|
High(1);
|
||||||
|
|
||||||
|
|
||||||
|
val isHigh: Boolean
|
||||||
|
get() = this == High
|
||||||
|
|
||||||
|
val isLow: Boolean = !!! this.isHigh
|
||||||
|
|
||||||
|
fun invert(): Bit {
|
||||||
|
if (this == High) {
|
||||||
|
return Low
|
||||||
|
}
|
||||||
|
|
||||||
|
return High
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package net.codinux.banking.ui.service.tan
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import net.codinux.banking.client.model.tan.FlickerCode
|
||||||
|
import net.codinux.log.logger
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
|
|
||||||
|
class FlickerCodeAnimator {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MinFrequency = 2
|
||||||
|
const val MaxFrequency = 40
|
||||||
|
const val DefaultFrequency = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var currentFrequency: Int = DefaultFrequency
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var isPaused = false
|
||||||
|
|
||||||
|
private var animationJob: Job? = null
|
||||||
|
|
||||||
|
private val log by logger()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun animateFlickerCode(flickerCode: FlickerCode, showStep: (Step) -> Unit) { // TODO: find better coroutine scope
|
||||||
|
stop() // stop may still running previous animation
|
||||||
|
|
||||||
|
animationJob = GlobalScope.launch(Dispatchers.Default) {
|
||||||
|
val steps = FlickerCodeStepsCalculator().calculateSteps(flickerCode.parsedDataSet)
|
||||||
|
|
||||||
|
calculateAnimation(steps, showStep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun calculateAnimation(steps: List<Step>, showStep: (Step) -> Unit) {
|
||||||
|
var currentStepIndex = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (isPaused == false) {
|
||||||
|
val nextStep = steps[currentStepIndex]
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showStep(nextStep)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStepIndex++
|
||||||
|
if (currentStepIndex >= steps.size) {
|
||||||
|
currentStepIndex = 0 // all steps shown, start again from beginning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1000L / currentFrequency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
this.isPaused = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
this.isPaused = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
try {
|
||||||
|
animationJob?.cancel()
|
||||||
|
|
||||||
|
animationJob = null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn(e) { "Could not stop animation job" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setFrequency(frequency: Int) {
|
||||||
|
if (frequency in MinFrequency..MaxFrequency) {
|
||||||
|
currentFrequency = frequency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package net.codinux.banking.ui.service.tan
|
||||||
|
|
||||||
|
open class FlickerCodeStepsCalculator {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
val bits = mutableMapOf<Char, Step>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
/* bitfield: clock, bits 2^1, 2^2, 2^3, 2^4 */
|
||||||
|
bits['0'] = Step(Bit.Low, Bit.Low, Bit.Low, Bit.Low, Bit.Low)
|
||||||
|
bits['1'] = Step(Bit.Low, Bit.High, Bit.Low, Bit.Low, Bit.Low)
|
||||||
|
bits['2'] = Step(Bit.Low, Bit.Low, Bit.High, Bit.Low, Bit.Low)
|
||||||
|
bits['3'] = Step(Bit.Low, Bit.High, Bit.High, Bit.Low, Bit.Low)
|
||||||
|
bits['4'] = Step(Bit.Low, Bit.Low, Bit.Low, Bit.High, Bit.Low)
|
||||||
|
bits['5'] = Step(Bit.Low, Bit.High, Bit.Low, Bit.High, Bit.Low)
|
||||||
|
bits['6'] = Step(Bit.Low, Bit.Low, Bit.High, Bit.High, Bit.Low)
|
||||||
|
bits['7'] = Step(Bit.Low, Bit.High, Bit.High, Bit.High, Bit.Low)
|
||||||
|
bits['8'] = Step(Bit.Low, Bit.Low, Bit.Low, Bit.Low, Bit.High)
|
||||||
|
bits['9'] = Step(Bit.Low, Bit.High, Bit.Low, Bit.Low, Bit.High)
|
||||||
|
bits['A'] = Step(Bit.Low, Bit.Low, Bit.High, Bit.Low, Bit.High)
|
||||||
|
bits['B'] = Step(Bit.Low, Bit.High, Bit.High, Bit.Low, Bit.High)
|
||||||
|
bits['C'] = Step(Bit.Low, Bit.Low, Bit.Low, Bit.High, Bit.High)
|
||||||
|
bits['D'] = Step(Bit.Low, Bit.High, Bit.Low, Bit.High, Bit.High)
|
||||||
|
bits['E'] = Step(Bit.Low, Bit.Low, Bit.High, Bit.High, Bit.High)
|
||||||
|
bits['F'] = Step(Bit.Low, Bit.High, Bit.High, Bit.High, Bit.High)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun calculateSteps(flickerCode: String): List<Step> {
|
||||||
|
|
||||||
|
val halfbyteid = ObjectHolder(0)
|
||||||
|
val clock = ObjectHolder(Bit.High)
|
||||||
|
val bitarray = mutableListOf<Step>()
|
||||||
|
|
||||||
|
|
||||||
|
/* prepend synchronization identifier */
|
||||||
|
var code = "0FFF" + flickerCode
|
||||||
|
if (code.length % 2 != 0) {
|
||||||
|
code += "F"
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until code.length step 2) {
|
||||||
|
bits[code[i + 1]]?.let { bitarray.add(it) }
|
||||||
|
bits[code[i]]?.let { bitarray.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val steps = mutableListOf<Step>()
|
||||||
|
|
||||||
|
do {
|
||||||
|
steps.add(calculateStep(halfbyteid, clock, bitarray))
|
||||||
|
} while (halfbyteid.value > 0 || clock.value == Bit.Low)
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun calculateStep(halfbyteid: ObjectHolder<Int>, clock: ObjectHolder<Bit>, bitarray: List<Step>): Step {
|
||||||
|
val step = Step(clock.value, bitarray[halfbyteid.value])
|
||||||
|
|
||||||
|
clock.value = clock.value.invert()
|
||||||
|
|
||||||
|
if (clock.value == Bit.High) {
|
||||||
|
halfbyteid.value++
|
||||||
|
|
||||||
|
if (halfbyteid.value >= bitarray.size) {
|
||||||
|
halfbyteid.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package net.codinux.banking.ui.service.tan
|
||||||
|
|
||||||
|
data class ObjectHolder<T>(var value: T)
|
|
@ -0,0 +1,16 @@
|
||||||
|
package net.codinux.banking.ui.service.tan
|
||||||
|
|
||||||
|
open class Step(
|
||||||
|
val bit1: Bit,
|
||||||
|
val bit2: Bit,
|
||||||
|
val bit3: Bit,
|
||||||
|
val bit4: Bit,
|
||||||
|
val bit5: Bit
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(clockBit: Bit, step: Step) : this(clockBit, step.bit2, step.bit3, step.bit4, step.bit5)
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString() = "${bit1}${bit2}${bit3}${bit4}$bit5"
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue