diff options
Diffstat (limited to 'cashier/src/main/java/net/taler')
11 files changed, 93 insertions, 70 deletions
diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt index fa9600b..4fd3143 100644 --- a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt +++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt @@ -54,19 +54,19 @@ class BalanceFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { setHasOptionsMenu(true) ui = FragmentBalanceBinding.inflate(layoutInflater, container, false) return ui.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - withdrawManager.lastTransaction.observe(viewLifecycleOwner, { lastTransaction -> + withdrawManager.lastTransaction.observe(viewLifecycleOwner) { lastTransaction -> onLastTransaction(lastTransaction) - }) - viewModel.balance.observe(viewLifecycleOwner, { result -> + } + viewModel.balance.observe(viewLifecycleOwner) { result -> onBalanceUpdated(result) - }) + } ui.button5.setOnClickListener { onAmountButtonPressed(5) } ui.button10.setOnClickListener { onAmountButtonPressed(10) } ui.button20.setOnClickListener { onAmountButtonPressed(20) } @@ -81,9 +81,9 @@ class BalanceFragment : Fragment() { true } else false } - configManager.currency.observe(viewLifecycleOwner, { currency -> + configManager.currency.observe(viewLifecycleOwner) { currency -> ui.currencyView.text = currency - }) + } ui.confirmWithdrawalButton.setOnClickListener { onAmountConfirmed(getAmountFromView()) } } @@ -104,11 +104,13 @@ class BalanceFragment : Fragment() { } } + @Deprecated("Deprecated in Java") override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.balance, menu) super.onCreateOptionsMenu(menu, inflater) } + @Deprecated("Deprecated in Java") override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_reconfigure -> { findNavController().navigate(configManager.configDestination) @@ -164,13 +166,19 @@ class BalanceFragment : Fragment() { private fun onAmountConfirmed(amount: Amount) { if (amount.isZero()) { ui.amountView.error = getString(R.string.withdraw_error_zero) - } else if (!withdrawManager.hasSufficientBalance(amount)) { - ui.amountView.error = getString(R.string.withdraw_error_insufficient_balance) - } else { - ui.amountView.error = null - withdrawManager.withdraw(amount) - actionBalanceFragmentToTransactionFragment().let { - findNavController().navigate(it) + } else when (withdrawManager.hasSufficientBalance(amount)) { + true -> { + ui.amountView.error = null + withdrawManager.withdraw(amount) + actionBalanceFragmentToTransactionFragment().let { + findNavController().navigate(it) + } + } + false -> { + ui.amountView.error = getString(R.string.withdraw_error_insufficient_balance) + } + null -> { + ui.amountView.error = getString(R.string.withdraw_error_currency_mismatch) } } } diff --git a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt index fd48b2d..69cc46f 100644 --- a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt +++ b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt @@ -50,11 +50,13 @@ object HttpHelper { Log.e(TAG, "Error retrieving $url", e) return HttpJsonResult.Error(0) } - return if (response.code == 200 && response.body != null) { + return if (response.code == 204) { + HttpJsonResult.Success(JSONObject()) + } else if (response.code in 200..299 && response.body != null) { val jsonObject = JSONObject(response.body!!.string()) HttpJsonResult.Success(jsonObject) } else { - Log.e(TAG, "Received status ${response.code} from $url expected 200") + Log.e(TAG, "Received status ${response.code} from $url expected 2xx") HttpJsonResult.Error(response.code, getErrorBody(response)) } } @@ -76,11 +78,13 @@ object HttpHelper { Log.e(TAG, "Error retrieving $url", e) return HttpJsonResult.Error(0) } - return if (response.code == 200 && response.body != null) { + return if (response.code == 204) { + HttpJsonResult.Success(JSONObject()) + } else if (response.code in 200..299 && response.body != null) { val jsonObject = JSONObject(response.body!!.string()) HttpJsonResult.Success(jsonObject) } else { - Log.e(TAG, "Received status ${response.code} from $url expected 200") + Log.e(TAG, "Received status ${response.code} from $url expected 2xx") HttpJsonResult.Error(response.code, getErrorBody(response)) } } diff --git a/cashier/src/main/java/net/taler/cashier/MainActivity.kt b/cashier/src/main/java/net/taler/cashier/MainActivity.kt index 2f4c4ec..aacc225 100644 --- a/cashier/src/main/java/net/taler/cashier/MainActivity.kt +++ b/cashier/src/main/java/net/taler/cashier/MainActivity.kt @@ -52,6 +52,7 @@ class MainActivity : AppCompatActivity() { } } + @Deprecated("Deprecated in Java") override fun onBackPressed() { if (!configManager.hasConfig() && nav.currentDestination?.id == R.id.configFragment) { // we are in the configuration screen and need a config to continue diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt index 21d0209..2196e78 100644 --- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt +++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt @@ -64,7 +64,7 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) { fun getBalance() = viewModelScope.launch(Dispatchers.IO) { check(configManager.hasConfig()) { "No config to get balance" } val config = configManager.config - val url = "${config.bankUrl}/access-api/accounts/${config.username}" + val url = "${config.bankUrl}/accounts/${config.username}" Log.d(TAG, "Checking balance at $url") val result = when (val response = makeJsonGetRequest(url, config)) { is HttpJsonResult.Success -> { diff --git a/cashier/src/main/java/net/taler/cashier/Response.kt b/cashier/src/main/java/net/taler/cashier/Response.kt index 89b7b33..e9db447 100644 --- a/cashier/src/main/java/net/taler/cashier/Response.kt +++ b/cashier/src/main/java/net/taler/cashier/Response.kt @@ -19,7 +19,6 @@ package net.taler.cashier import android.content.Context import android.util.Log import io.ktor.client.call.body -import io.ktor.client.call.receive import io.ktor.client.plugins.ResponseException import io.ktor.http.HttpStatusCode import kotlinx.serialization.Serializable @@ -47,7 +46,6 @@ class Response<out T> private constructor( private suspend fun getExceptionString(e: ResponseException): String { val response = e.response return try { - Log.e("TEST", "TRY RECEIVE $response") val error: Error = response.body() "Error ${error.code}: ${error.hint}" } catch (ex: Exception) { diff --git a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt index 4f624ae..45bc3af 100644 --- a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt +++ b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt @@ -23,8 +23,10 @@ data class SignedAmount( val amount: Amount ) { - override fun toString(): String { - return if (positive) "$amount" else "-$amount" - } + override fun toString() = toString(showSymbol = true) + fun toString(showSymbol: Boolean) = amount.toString( + showSymbol = showSymbol, + negative = !positive, + ) } diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt index c9e87f8..3085bef 100644 --- a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt +++ b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt @@ -39,8 +39,8 @@ import net.taler.cashier.databinding.FragmentConfigBinding import net.taler.common.exhaustive import net.taler.common.showError -private const val URL_BANK_TEST = "https://bank.demo.taler.net/demobanks/default" -private const val URL_BANK_TEST_REGISTER = "$URL_BANK_TEST/accounts/register" +private const val URL_BANK_TEST = "https://bank.demo.taler.net" +private const val URL_BANK_TEST_REGISTER = "https://bank.demo.taler.net/webui/#/register" class ConfigFragment : Fragment() { @@ -74,9 +74,9 @@ class ConfigFragment : Fragment() { } ui.saveButton.setOnClickListener { val config = Config( - bankUrl = ui.urlView.editText!!.text.toString(), - username = ui.usernameView.editText!!.text.toString(), - password = ui.passwordView.editText!!.text.toString() + bankUrl = ui.urlView.editText!!.text.toString().trim(), + username = ui.usernameView.editText!!.text.toString().trim(), + password = ui.passwordView.editText!!.text.toString().trim() ) if (checkConfig(config)) { // show progress @@ -111,9 +111,9 @@ class ConfigFragment : Fragment() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // for some reason automatic restore isn't working at the moment!? - outState.putCharSequence("urlView", ui.urlView.editText?.text) - outState.putCharSequence("usernameView", ui.usernameView.editText?.text) - outState.putCharSequence("passwordView", ui.passwordView.editText?.text) + outState.putCharSequence("urlView", ui.urlView.editText?.text?.trim()) + outState.putCharSequence("usernameView", ui.usernameView.editText?.text?.trim()) + outState.putCharSequence("passwordView", ui.passwordView.editText?.text?.trim()) } private fun checkConfig(config: Config): Boolean { diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt index d850d27..50b1faf 100644 --- a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt +++ b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt @@ -38,12 +38,13 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import net.taler.cashier.BuildConfig import net.taler.cashier.Response import net.taler.cashier.Response.Companion.response import net.taler.common.Version import net.taler.common.getIncompatibleStringOrNull -val VERSION_BANK = Version(0, 0, 0) +val VERSION_BANK = Version.parse(BuildConfig.BACKEND_API_VERSION)!! private const val PREF_NAME = "net.taler.cashier.prefs" private const val PREF_KEY_BANK_URL = "bankUrl" private const val PREF_KEY_USERNAME = "username" @@ -55,7 +56,7 @@ private val TAG = ConfigManager::class.java.simpleName class ConfigManager( private val app: Application, private val scope: CoroutineScope, - private val httpClient: HttpClient + private val httpClient: HttpClient, ) { val configDestination = ConfigFragmentDirections.actionGlobalConfigFragment() @@ -112,8 +113,9 @@ class ConfigManager( mConfigResult.postValue(result) } } + private suspend fun checkConfig(config: Config) = withContext(Dispatchers.IO) { - val url = "${config.bankUrl}/integration-api/config" + val url = "${config.bankUrl}/config" Log.d(TAG, "Checking config: $url") val configResponse = response { httpClient.get(url).body<ConfigResponse>() @@ -124,7 +126,7 @@ class ConfigManager( // we need to check an endpoint that requires authentication as well // to see if the credentials are valid val balanceResponse = response { - val authUrl = "${config.bankUrl}/access-api/accounts/${config.username}" + val authUrl = "${config.bankUrl}/accounts/${config.username}" Log.d(TAG, "Checking auth: $authUrl") httpClient.get(authUrl) { header(Authorization, config.basicAuth) diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt index 4f98847..c951bb8 100644 --- a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt +++ b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt @@ -22,7 +22,6 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import net.taler.cashier.MainViewModel import net.taler.cashier.R @@ -37,22 +36,22 @@ class ErrorFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { + savedInstanceState: Bundle?, + ): View { ui = FragmentErrorBinding.inflate(inflater, container, false) return ui.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { status -> - if (status == null) return@Observer + withdrawManager.withdrawStatus.observe(viewLifecycleOwner) { status -> + if (status == null) return@observe if (status is WithdrawStatus.Aborted) { ui.textView.setText(R.string.transaction_aborted) } else if (status is WithdrawStatus.Error) { ui.textView.text = status.msg } withdrawManager.completeTransaction() - }) + } ui.backButton.setOnClickListener { findNavController().popBackStack() } diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt index ffb1539..0f606b8 100644 --- a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt +++ b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt @@ -50,21 +50,21 @@ class TransactionFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { ui = FragmentTransactionBinding.inflate(inflater, container, false) return ui.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - withdrawManager.withdrawAmount.observe(viewLifecycleOwner, { amount -> + withdrawManager.withdrawAmount.observe(viewLifecycleOwner) { amount -> ui.amountView.text = amount?.toString() - }) - withdrawManager.withdrawResult.observe(viewLifecycleOwner, { result -> + } + withdrawManager.withdrawResult.observe(viewLifecycleOwner) { result -> onWithdrawResultReceived(result) - }) - withdrawManager.withdrawStatus.observe(viewLifecycleOwner, { status -> + } + withdrawManager.withdrawStatus.observe(viewLifecycleOwner) { status -> onWithdrawStatusChanged(status) - }) + } // change intro text depending on whether NFC is available or not val hasNfc = NfcManager.hasNfc(requireContext()) diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt index 1b809bb..487475d 100644 --- a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt +++ b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt @@ -38,17 +38,15 @@ import net.taler.common.Amount import net.taler.common.QrCodeManager.makeQrCode import net.taler.common.isOnline import org.json.JSONObject -import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.SECONDS private val TAG = WithdrawManager::class.java.simpleName private val INTERVAL = SECONDS.toMillis(1) -private val TIMEOUT = MINUTES.toMillis(2) class WithdrawManager( private val app: Application, - private val viewModel: MainViewModel + private val viewModel: MainViewModel, ) { private val scope get() = viewModel.viewModelScope @@ -73,11 +71,20 @@ class WithdrawManager( private val mLastTransaction = MutableLiveData<LastTransaction>() val lastTransaction: LiveData<LastTransaction> = mLastTransaction + /** + * Returns null if the given [amount] can't be compared to the balance + * e.g. due to mismatching currency. + */ @UiThread - fun hasSufficientBalance(amount: Amount): Boolean { + fun hasSufficientBalance(amount: Amount): Boolean? { val balanceResult = viewModel.balance.value if (balanceResult !is BalanceResult.Success) return false - return balanceResult.amount.positive && amount <= balanceResult.amount.amount + return try { + balanceResult.amount.positive && amount <= balanceResult.amount.amount + } catch (e: IllegalStateException) { + Log.e(TAG, "Error comparing amounts", e) + null + } } @UiThread @@ -87,7 +94,7 @@ class WithdrawManager( mWithdrawResult.value = null mWithdrawAmount.value = amount scope.launch(Dispatchers.IO) { - val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals" + val url = "${config.bankUrl}/accounts/${config.username}/withdrawals" Log.d(TAG, "Starting withdrawal at $url") val map = mapOf("amount" to amount.toJSONString()) val body = JSONObject(map) @@ -119,7 +126,7 @@ class WithdrawManager( } } - private val timer: CountDownTimer = object : CountDownTimer(TIMEOUT, INTERVAL) { + private val timer: CountDownTimer = object : CountDownTimer(Long.MAX_VALUE, INTERVAL) { override fun onTick(millisUntilFinished: Long) { val result = withdrawResult.value if (result is WithdrawResult.Success) { @@ -146,31 +153,32 @@ class WithdrawManager( } private fun checkWithdrawStatus(withdrawalId: String) = scope.launch(Dispatchers.IO) { - val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}" + val url = + "${config.bankUrl}/withdrawals/${withdrawalId}" Log.d(TAG, "Checking withdraw status at $url") val response = makeJsonGetRequest(url, config) if (response !is Success) return@launch // ignore errors and continue trying val oldStatus = mWithdrawStatus.value try { - when { - response.json.getBoolean("aborted") -> { + when(response.json.getString("status")) { + "selected" -> { + // only update status, if there's none, yet + // so we don't re-notify or overwrite newer status info + if (oldStatus == null) { + mWithdrawStatus.postValue(WithdrawStatus.SelectionDone(withdrawalId)) + } + } + "aborted" -> { cancelWithdrawStatusCheck() mWithdrawStatus.postValue(WithdrawStatus.Aborted) } - response.json.getBoolean("confirmation_done") -> { + "confirmed" -> { if (oldStatus !is WithdrawStatus.Success) { cancelWithdrawStatusCheck() mWithdrawStatus.postValue(WithdrawStatus.Success) viewModel.getBalance() } } - response.json.getBoolean("selection_done") -> { - // only update status, if there's none, yet - // so we don't re-notify or overwrite newer status info - if (oldStatus == null) { - mWithdrawStatus.postValue(WithdrawStatus.SelectionDone(withdrawalId)) - } - } } } catch (e: Exception) { mWithdrawStatus.postValue(WithdrawStatus.Error(e.toString())) @@ -197,7 +205,8 @@ class WithdrawManager( } private fun abort(withdrawalId: String) = scope.launch(Dispatchers.IO) { - val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}/abort" + val url = + "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/abort" Log.d(TAG, "Aborting withdrawal at $url") makeJsonPostRequest(url, JSONObject(), config) } @@ -207,7 +216,7 @@ class WithdrawManager( mWithdrawStatus.value = WithdrawStatus.Confirming scope.launch(Dispatchers.IO) { val url = - "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}/confirm" + "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/confirm" Log.d(TAG, "Confirming withdrawal at $url") when (val response = makeJsonPostRequest(url, JSONObject(), config)) { is Success -> { @@ -254,5 +263,5 @@ sealed class WithdrawStatus { data class LastTransaction( val withdrawAmount: Amount, - val withdrawStatus: WithdrawStatus + val withdrawStatus: WithdrawStatus, ) |