Skip to content

Commit

Permalink
Modifications in UI and logic in library
Browse files Browse the repository at this point in the history
  • Loading branch information
guillermovogel committed Jul 17, 2023
1 parent c7743b2 commit 9519969
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 143 deletions.
110 changes: 30 additions & 80 deletions app/src/main/java/com/vogel/nfc_reader/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.lifecycleScope
import com.vogel.nfc_reader.nfc.model.CardData
import com.vogel.nfc_reader.nfc.utils.NFCState
import com.vogel.nfc_reader.ui.theme.NFCReaderTheme
import dagger.hilt.android.AndroidEntryPoint

Expand All @@ -30,97 +31,46 @@ class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val cardStateText = mutableStateOf("")
val settingsButtonVisible = mutableStateOf(false)

lifecycleScope.launchWhenStarted {
viewModel.cardReaderObservable.event.collect {
settingsButtonVisible.value = false
cardStateText.value = when (it) {
NFCState.CardLost -> "Keep it steady. Card lost!"
is NFCState.Error -> getErrorLabel(it.throwable)
NFCState.Disabled -> {
settingsButtonVisible.value = true
"NFC Disabled"
}

NFCState.NotSupported -> "NFC Not Supported"
NFCState.ReadyToScan -> "Ready to Scan"
NFCState.StartReading -> "Reading card..."
is NFCState.Success -> getCardLabel(it.card)
}
}
}
setContent {
NFCReaderTheme {
CardDataScreen(
data = cardStateText.value,
settingsButtonVisible = settingsButtonVisible.value
) {
viewModel.cardReader.openSettings(this)
}
CardDataScreen()
}
}
}

override fun onResume() {
super.onResume()
viewModel.cardReaderObservable.start(this)
}

override fun onPause() {
super.onPause()
viewModel.cardReaderObservable.stop(this)
viewModel.cardReaderListener.stop(this)
}

companion object {
internal fun getCardLabel(cardData: CardData): String {
return """
AID: ${cardData.aids.joinToString(" | ")}
Type: ${cardData.types.joinToString(" | ")}
State: ${cardData.state.name}
Number: ${cardData.formattedNumber}
Expires: ${cardData.formattedDate}
Valid: ${cardData.isValid}
Holder: ${cardData.holderLastName}
""".trimIndent()
}
}

internal fun getErrorLabel(error: Throwable): String {
return """
ERROR
Message: ${error.message ?: error.cause?.message}
""".trimIndent()
}
@Composable
fun CardDataScreen(
viewModel: MainViewModel = hiltViewModel()
) {

val state = viewModel.uiState
val context = LocalContext.current

LaunchedEffect(Unit) {
viewModel.cardReaderListener.start(context as MainActivity)
}

@Composable
fun CardDataScreen(
data: String,
settingsButtonVisible: Boolean,
openNfcSettings: () -> Unit = {}
Column(
modifier = Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.background(MaterialTheme.colors.background)
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = data)
if (settingsButtonVisible) {
Button(
onClick = openNfcSettings,
modifier = Modifier.padding(top = 15.dp)
) {
Text("SETTINGS")
}
Text(text = state.cardStateText)
if (state.settingsButtonVisible) {
Button(
onClick = { viewModel.cardReader.openSettings(context) },
modifier = Modifier.padding(top = 15.dp)
) {
Text("SETTINGS")
}
}
}
Expand Down
55 changes: 51 additions & 4 deletions app/src/main/java/com/vogel/nfc_reader/MainViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,58 @@
package com.vogel.nfc_reader


import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vogel.nfc_reader.nfc.api.CardReader
import com.vogel.nfc_reader.nfc.api.CardReaderObservable
import com.vogel.nfc_reader.nfc.api.CardReaderListener
import com.vogel.nfc_reader.nfc.model.CardData
import com.vogel.nfc_reader.nfc.utils.NFCState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {


class MainViewModel : ViewModel() {
val cardReader = CardReader.newInstance()
val cardReaderObservable = CardReaderObservable.newInstance(cardReader)
}
val cardReaderListener = CardReaderListener.newInstance(cardReader)

var uiState by mutableStateOf(UiState())
private set

init {
listener()
}

fun listener() {
viewModelScope.launch {
cardReaderListener.nfcStatus.collect {
uiState = uiState.copy(settingsButtonVisible = false)
uiState = uiState.copy(
cardStateText = when (it) {
NFCState.CardLost -> "Keep it steady, card lost!"
is NFCState.Error -> CardData.fetchErrorInformation(it.throwable)
NFCState.Disabled -> {
uiState = uiState.copy(settingsButtonVisible = true)
"NFC is disabled. Go to settings to activate it"
}

NFCState.NotSupported -> "NFC is not supported in this device"
NFCState.ReadyToScan -> "The device is ready to scan"
NFCState.StartReading -> "The device is reading the card..."
is NFCState.Success -> CardData.fetchCardInformation(it.card)
}
)
}
}
}
}

data class UiState(
val cardStateText: String = "",
val settingsButtonVisible: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import android.nfc.tech.IsoDep
import com.github.devnied.emvnfccard.parser.EmvTemplate
import com.vogel.nfc_reader.nfc.model.CardData
import com.vogel.nfc_reader.nfc.provider.TransceiverProvider
import com.vogel.nfc_reader.nfc.reader.CardReaderImpl
import com.vogel.nfc_reader.nfc.reader.NfcIntentProvider
import com.vogel.nfc_reader.nfc.implementations.ReaderImplementation

interface CardReader {
fun getCard(isoDep: IsoDep): CardData
fun getCardResult(isoDep: IsoDep): Result<CardData>
fun openSettings(context: Context){}
fun openSettings(context: Context) {}

companion object {
fun newInstance(): CardReader {
Expand All @@ -23,11 +21,10 @@ interface CardReader {
.setReadAt(true) // To extract ATS or ATR
.setReadCplc(false) // To read CPLC data. Not for contactless cards.

return CardReaderImpl(
return ReaderImplementation(
config = config,
builder = EmvTemplate.Builder(),
provider = TransceiverProvider(),
intentProvider = NfcIntentProvider()
provider = TransceiverProvider()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ package com.vogel.nfc_reader.nfc.api

import android.app.Activity
import android.content.Context
import com.vogel.nfc_reader.nfc.reader.CardReaderObservableImpl
import com.vogel.nfc_reader.nfc.implementations.ListenerImplementation
import com.vogel.nfc_reader.nfc.utils.NFCState
import kotlinx.coroutines.flow.Flow

interface CardReaderObservable {
val event: Flow<NFCState>
interface CardReaderListener {
val nfcStatus: Flow<NFCState>

fun start(activity: Activity)
fun stop(activity: Activity)
fun openSettings(context: Context){}

companion object {
fun newInstance(cardReader: CardReader): CardReaderObservable {
return CardReaderObservableImpl(cardReader)
fun newInstance(cardReader: CardReader): CardReaderListener {
return ListenerImplementation(cardReader)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.vogel.nfc_reader.nfc.reader
package com.vogel.nfc_reader.nfc.implementations

import android.app.Activity
import android.content.Context
Expand All @@ -7,25 +7,27 @@ import android.nfc.Tag
import android.nfc.TagLostException
import android.nfc.tech.IsoDep
import com.vogel.nfc_reader.nfc.api.CardReader
import com.vogel.nfc_reader.nfc.api.CardReaderObservable
import com.vogel.nfc_reader.nfc.api.CardReaderListener
import com.vogel.nfc_reader.nfc.utils.NFCState
import com.vogel.nfc_reader.nfc.utils.NFCUtils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull

// This class is responsible for observing the NFC state of the user's device.
// It is also responsible for reading the NFC card and returning the result.
internal class CardReaderObservableImpl constructor(
/**
* This class is responsible for observing the NFC state of the user's device.
* It is also responsible for reading the NFC card and returning the result.
*/
class ListenerImplementation constructor(
private val cardReader: CardReader
) : CardReaderObservable, NfcAdapter.ReaderCallback {
) : CardReaderListener, NfcAdapter.ReaderCallback {

private var adapter: NfcAdapter? = null

private val _event = MutableStateFlow<NFCState?>(null)
private val _nfcStatus = MutableStateFlow<NFCState?>(null)

override val event: Flow<NFCState>
get() = _event.filterNotNull()
override val nfcStatus: Flow<NFCState>
get() = _nfcStatus.filterNotNull()

/**
* This function starts the NFC reader to scan for NFC cards.
Expand All @@ -37,16 +39,16 @@ internal class CardReaderObservableImpl constructor(
adapter = NFCUtils.getNfcAdapter(activity)?.apply {
// NFC Supported
if (!isEnabled) {
_event.tryEmit(NFCState.Disabled)
_nfcStatus.tryEmit(NFCState.Disabled)
adapter = null
return
}
if (adapter == null) {
_event.tryEmit(NFCState.ReadyToScan)
_nfcStatus.tryEmit(NFCState.ReadyToScan)
}
} ?: run {
// NFC Not Supported
_event.tryEmit(NFCState.NotSupported)
_nfcStatus.tryEmit(NFCState.NotSupported)
null
}

Expand All @@ -73,20 +75,21 @@ internal class CardReaderObservableImpl constructor(
/**
* This function allows the user to open the NFC settings on their device
* if they have NFC disabled, in order to enable it.
* This function can be removed if not necessary.
*/
override fun openSettings(context: Context) {
cardReader.openSettings(context)
}

/**
* When a tag is discovered, this function is called.
* When a tag is discovered or detected, this function is called.
* If the tag is successfully read, then the result is emitted, otherwise
* an error is emitted with the corresponding error message.
*/
override fun onTagDiscovered(tag: Tag?) {
_event.tryEmit(NFCState.StartReading)
_nfcStatus.tryEmit(NFCState.StartReading)
val isoDep = IsoDep.get(tag)
val resultEvent = cardReader.getCardResult(isoDep).fold(
val scanCardResult = cardReader.getCardResult(isoDep).fold(
onSuccess = NFCState::Success,
onFailure = {
when (it) {
Expand All @@ -95,6 +98,6 @@ internal class CardReaderObservableImpl constructor(
}
}
)
_event.tryEmit(resultEvent)
_nfcStatus.tryEmit(scanCardResult)
}
}
Loading

0 comments on commit 9519969

Please sign in to comment.