BuiltInOptions.kt
/*
* This file is part of GNU Taler
* (C) 2026 Taler Systems S.A.
*
* GNU Taler is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3, or (at your option) any later version.
*
* GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
package net.taler.merchantpos.paymentoptions
import android.content.Context
import android.nfc.NfcAdapter
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Payments
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import net.taler.merchantpos.R
import net.taler.merchantpos.devices.TalerBleStatus
import net.taler.merchantpos.devices.TalerBluetoothDevice
import net.taler.merchantpos.devices.TalerDevicesContent
import net.taler.merchantpos.devices.TalerUsbDevices
import net.taler.merchantpos.devices.TalerUsbStatus
import net.taler.merchantpos.devices.label
/** Taler is always the last payment of a checkout, so others look it up by id. */
const val TALER_OPTION_ID = "taler"
/**
* QR payments remain available regardless of NFC or external display status.
*/
class TalerOption(
private val context: Context,
private val usb: TalerUsbDevices? = null,
private val bluetooth: TalerBluetoothDevice? = null,
) : PaymentOption
{
override val id = TALER_OPTION_ID
override val nameRes = R.string.payment_option_taler
override val descriptionRes = R.string.payment_option_taler_description
override val enabledByDefault = true
override val alwaysEnabled = true
override val hasSettings get() = usb != null && bluetooth != null
override val settingsTitleRes = R.string.taler_devices_manage
override val icon: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.ic_taler_logo)
override val logo: ImageVector
@Composable get() = ImageVector.vectorResource(R.drawable.ic_taler_logo_full)
override fun probe(): PaymentOptionState
{
usb?.refresh()
return PaymentOptionState(
id = id,
enabled = true,
status = PaymentOptionStatus.Ready,
devices =
listOfNotNull(nfcDevice(), bluetoothDevice()) +
usb
?.state
?.value
?.takeIf { it.enabled }
?.devices
.orEmpty()
.mapIndexed { index, device ->
PaymentDevice(
id = "usb:${device.id}",
name = context.getString(R.string.taler_usb_device, index + 1),
connected = device.status == TalerUsbStatus.Ready,
statusRes =
when (device.status)
{
TalerUsbStatus.Ready -> null
TalerUsbStatus.PermissionRequired -> R.string.taler_usb_permission_required
TalerUsbStatus.Failed -> R.string.taler_usb_failed
},
)
},
)
}
override fun onLogout()
{
usb?.logout()
bluetooth?.logout()
}
private fun bluetoothDevice(): PaymentDevice?
{
val state = bluetooth?.state?.value?.takeIf { it.enabled } ?: return null
val selected = state.selected ?: return null
return PaymentDevice(
id = "ble:${selected.address}",
name = selected.name,
connected = state.status in setOf(TalerBleStatus.Ready, TalerBleStatus.Sending, TalerBleStatus.TooLong),
statusRes = state.status.label(),
)
}
@Composable
override fun DetailContent(
state: PaymentOptionState,
actions: PaymentOptionActions,
)
{
if (bluetooth != null && usb != null)
{
TalerDevicesContent(bluetooth, usb)
}
}
private fun nfcDevice(): PaymentDevice?
{
// A tablet without NFC hardware has nothing to report; the QR code still works.
val adapter = NfcAdapter.getDefaultAdapter(context) ?: return null
val enabled = adapter.isEnabled
return PaymentDevice(
id = "nfc",
nameRes = R.string.payment_device_nfc,
connected = enabled,
statusRes =
if (enabled)
{
R.string.payment_device_nfc_available
}
else
{
R.string.payment_option_disabled
},
)
}
}
/** Taking cash at the counter. Settled outside the app, so it only records the choice. */
object CashOption : PaymentOption
{
override val id = "cash"
override val nameRes = R.string.payment_option_cash
override val descriptionRes = R.string.payment_option_cash_description
override val icon: ImageVector
@Composable get() = Icons.Default.Payments
}