diff options
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/history')
6 files changed, 932 insertions, 0 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt new file mode 100644 index 0000000..88db90c --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryAdapter.kt @@ -0,0 +1,133 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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.wallet.history + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import net.taler.common.exhaustive +import net.taler.common.toRelativeTime +import net.taler.wallet.R +import net.taler.wallet.history.DevHistoryAdapter.HistoryViewHolder + +@Deprecated("Replaced by TransactionAdapter") +internal class DevHistoryAdapter( + private val listener: OnEventClickListener, + private var history: History = History() +) : Adapter<HistoryViewHolder>() { + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_history, parent, false) + return HistoryViewHolder(view) + } + + override fun getItemCount(): Int = history.size + + override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) { + val transaction = history[position] + holder.bind(transaction) + } + + fun update(updatedHistory: History) { + this.history = updatedHistory + this.notifyDataSetChanged() + } + + internal open inner class HistoryViewHolder(private val v: View) : ViewHolder(v) { + + protected val context: Context = v.context + + private val icon: ImageView = v.findViewById(R.id.icon) + protected val title: TextView = v.findViewById(R.id.title) + private val time: TextView = v.findViewById(R.id.time) + private val amount: TextView = v.findViewById(R.id.amount) + + private val amountColor = amount.currentTextColor + + open fun bind(historyEvent: HistoryEvent) { + v.setOnClickListener { listener.onTransactionClicked(historyEvent) } + icon.setImageResource(historyEvent.icon) + + title.text = if (historyEvent.title == null) { + when (historyEvent) { + is RefreshHistoryEvent -> getRefreshTitle(historyEvent) + is OrderAcceptedHistoryEvent -> context.getString(R.string.transaction_order_accepted) + is OrderRefusedHistoryEvent -> context.getString(R.string.transaction_order_refused) + is TipAcceptedHistoryEvent -> context.getString(R.string.transaction_tip_accepted) + is TipDeclinedHistoryEvent -> context.getString(R.string.transaction_tip_declined) + is ReserveBalanceUpdatedHistoryEvent -> context.getString(R.string.transaction_reserve_balance_updated) + else -> historyEvent::class.java.simpleName + } + } else historyEvent.title + + time.text = historyEvent.timestamp.ms.toRelativeTime(context) + bindAmount(historyEvent.displayAmount) + } + + private fun bindAmount(displayAmount: DisplayAmount?) { + if (displayAmount == null) { + amount.visibility = GONE + } else { + amount.visibility = VISIBLE + when (displayAmount.type) { + AmountType.Positive -> { + amount.text = context.getString( + R.string.amount_positive, displayAmount.amount.amountStr + ) + amount.setTextColor(context.getColor(R.color.green)) + } + AmountType.Negative -> { + amount.text = context.getString( + R.string.amount_negative, displayAmount.amount.amountStr + ) + amount.setTextColor(context.getColor(R.color.red)) + } + AmountType.Neutral -> { + amount.text = displayAmount.amount.amountStr + amount.setTextColor(amountColor) + } + }.exhaustive + } + } + + private fun getRefreshTitle(transaction: RefreshHistoryEvent): String { + val res = when (transaction.refreshReason) { + RefreshReason.MANUAL -> R.string.transaction_refresh_reason_manual + RefreshReason.PAY -> R.string.transaction_refresh_reason_pay + RefreshReason.REFUND -> R.string.transaction_refresh_reason_refund + RefreshReason.ABORT_PAY -> R.string.transaction_refresh_reason_abort_pay + RefreshReason.RECOUP -> R.string.transaction_refresh_reason_recoup + RefreshReason.BACKUP_RESTORED -> R.string.transaction_refresh_reason_backup_restored + } + return context.getString(R.string.transaction_refresh) + " " + context.getString(res) + } + + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt new file mode 100644 index 0000000..c3c07a3 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryFragment.kt @@ -0,0 +1,87 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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.wallet.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL +import kotlinx.android.synthetic.main.fragment_transactions.* +import net.taler.common.fadeIn +import net.taler.common.fadeOut +import net.taler.wallet.MainViewModel +import net.taler.wallet.R + +internal interface OnEventClickListener { + fun onTransactionClicked(historyEvent: HistoryEvent) +} + +class DevHistoryFragment : Fragment(), + OnEventClickListener { + + private val model: MainViewModel by activityViewModels() + private val historyManager by lazy { model.historyManager } + private val historyAdapter by lazy { DevHistoryAdapter(this) } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_transactions, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (savedInstanceState == null) historyManager.loadHistory() + + list.apply { + adapter = historyAdapter + addItemDecoration(DividerItemDecoration(context, VERTICAL)) + } + historyManager.progress.observe(viewLifecycleOwner, Observer { show -> + progressBar.visibility = if (show) VISIBLE else INVISIBLE + }) + historyManager.history.observe(viewLifecycleOwner, Observer { result -> + onHistoryResult(result) + }) + } + + override fun onTransactionClicked(historyEvent: HistoryEvent) { + JsonDialogFragment.new(historyEvent.json.toString(2)) + .show(parentFragmentManager, null) + } + + private fun onHistoryResult(result: HistoryResult) = when (result) { + HistoryResult.Error -> { + list.fadeOut() + emptyState.text = getString(R.string.transactions_error) + emptyState.fadeIn() + } + is HistoryResult.Success -> { + emptyState.visibility = if (result.history.isEmpty()) VISIBLE else INVISIBLE + historyAdapter.update(result.history) + list.fadeIn() + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt new file mode 100644 index 0000000..72967b2 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/DevHistoryManager.kt @@ -0,0 +1,78 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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.wallet.history + +import androidx.annotation.UiThread +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import net.taler.wallet.backend.WalletBackendApi +import org.json.JSONObject + +sealed class HistoryResult { + object Error : HistoryResult() + class Success(val history: History) : HistoryResult() +} + +class DevHistoryManager( + private val walletBackendApi: WalletBackendApi, + private val scope: CoroutineScope, + private val mapper: ObjectMapper +) { + + private val mProgress = MutableLiveData<Boolean>() + val progress: LiveData<Boolean> = mProgress + + private val mHistory = MutableLiveData<HistoryResult>() + val history: LiveData<HistoryResult> = mHistory + + @UiThread + internal fun loadHistory() { + mProgress.value = true + walletBackendApi.sendRequest("getHistory", null) { isError, result -> + scope.launch(Dispatchers.Default) { + onEventsLoaded(isError, result) + } + } + } + + private fun onEventsLoaded(isError: Boolean, result: JSONObject) { + if (isError) { + mHistory.postValue(HistoryResult.Error) + return + } + val history = History() + val json = result.getJSONArray("history") + for (i in 0 until json.length()) { + val event: HistoryEvent = mapper.readValue(json.getString(i)) + event.json = json.getJSONObject(i) + history.add(event) + } + history.reverse() // show latest first + mProgress.postValue(false) + mHistory.postValue( + HistoryResult.Success( + history + ) + ) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt new file mode 100644 index 0000000..acca679 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/HistoryEvent.kt @@ -0,0 +1,518 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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.wallet.history + +import androidx.annotation.DrawableRes +import androidx.annotation.LayoutRes +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonSubTypes.Type +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME +import com.fasterxml.jackson.annotation.JsonTypeName +import net.taler.common.Amount +import net.taler.common.Timestamp +import net.taler.wallet.R +import net.taler.wallet.cleanExchange +import org.json.JSONObject + +enum class ReserveType { + /** + * Manually created. + */ + @JsonProperty("manual") + MANUAL, + + /** + * Withdrawn from a bank that has "tight" Taler integration + */ + @JsonProperty("taler-bank-withdraw") + @Suppress("unused") + TALER_BANK_WITHDRAW, +} + +@JsonInclude(NON_EMPTY) +class ReserveCreationDetail(val type: ReserveType, val bankUrl: String?) + +enum class RefreshReason { + @JsonProperty("manual") + @Suppress("unused") + MANUAL, + + @JsonProperty("pay") + PAY, + + @JsonProperty("refund") + @Suppress("unused") + REFUND, + + @JsonProperty("abort-pay") + @Suppress("unused") + ABORT_PAY, + + @JsonProperty("recoup") + @Suppress("unused") + RECOUP, + + @JsonProperty("backup-restored") + @Suppress("unused") + BACKUP_RESTORED +} + +@JsonInclude(NON_EMPTY) +class ReserveShortInfo( + /** + * The exchange that the reserve will be at. + */ + val exchangeBaseUrl: String, + /** + * Key to query more details + */ + val reservePub: String, + /** + * Detail about how the reserve has been created. + */ + val reserveCreationDetail: ReserveCreationDetail +) + +sealed class AmountType { + object Positive : AmountType() + object Negative : AmountType() + object Neutral : AmountType() +} + +class DisplayAmount( + val amount: Amount, + val type: AmountType +) + +typealias History = ArrayList<HistoryEvent> + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type", + defaultImpl = UnknownHistoryEvent::class +) +/** missing: +AuditorComplaintSent = "auditor-complained-sent", +AuditorComplaintProcessed = "auditor-complaint-processed", +AuditorTrustAdded = "auditor-trust-added", +AuditorTrustRemoved = "auditor-trust-removed", +ExchangeTermsAccepted = "exchange-terms-accepted", +ExchangePolicyChanged = "exchange-policy-changed", +ExchangeTrustAdded = "exchange-trust-added", +ExchangeTrustRemoved = "exchange-trust-removed", +FundsDepositedToSelf = "funds-deposited-to-self", +FundsRecouped = "funds-recouped", +ReserveCreated = "reserve-created", + */ +@JsonSubTypes( + Type(value = ExchangeAddedEvent::class, name = "exchange-added"), + Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"), + Type(value = ReserveBalanceUpdatedHistoryEvent::class, name = "reserve-balance-updated"), + Type(value = WithdrawHistoryEvent::class, name = "withdrawn"), + Type(value = OrderAcceptedHistoryEvent::class, name = "order-accepted"), + Type(value = OrderRefusedHistoryEvent::class, name = "order-refused"), + Type(value = OrderRedirectedHistoryEvent::class, name = "order-redirected"), + Type(value = PaymentHistoryEvent::class, name = "payment-sent"), + Type(value = PaymentAbortedHistoryEvent::class, name = "payment-aborted"), + Type(value = TipAcceptedHistoryEvent::class, name = "tip-accepted"), + Type(value = TipDeclinedHistoryEvent::class, name = "tip-declined"), + Type(value = RefundHistoryEvent::class, name = "refund"), + Type(value = RefreshHistoryEvent::class, name = "refreshed") +) +abstract class HistoryEvent( + val timestamp: Timestamp, + val eventId: String, + @get:LayoutRes + open val detailPageLayout: Int = 0, + @get:DrawableRes + open val icon: Int = R.drawable.ic_account_balance, + open val showToUser: Boolean = false +) { + abstract val title: String? + open lateinit var json: JSONObject + open val displayAmount: DisplayAmount? = null + open fun isCurrency(currency: String): Boolean = true +} + + +class UnknownHistoryEvent(timestamp: Timestamp, eventId: String) : HistoryEvent(timestamp, eventId) { + override val title: String? = null +} + +@JsonTypeName("exchange-added") +class ExchangeAddedEvent( + timestamp: Timestamp, + eventId: String, + val exchangeBaseUrl: String, + val builtIn: Boolean +) : HistoryEvent(timestamp, eventId) { + override val title = cleanExchange(exchangeBaseUrl) +} + +@JsonTypeName("exchange-updated") +class ExchangeUpdatedEvent( + timestamp: Timestamp, + eventId: String, + val exchangeBaseUrl: String +) : HistoryEvent(timestamp, eventId) { + override val title = cleanExchange(exchangeBaseUrl) +} + + +@JsonTypeName("reserve-balance-updated") +class ReserveBalanceUpdatedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Condensed information about the reserve. + */ + val reserveShortInfo: ReserveShortInfo, + /** + * Amount currently left in the reserve. + */ + val reserveBalance: Amount, + /** + * Amount we expected to be in the reserve at that time, + * considering ongoing withdrawals from that reserve. + */ + val reserveAwaitedAmount: Amount, + /** + * Amount that hasn't been withdrawn yet. + */ + val reserveUnclaimedAmount: Amount +) : HistoryEvent(timestamp, eventId) { + override val title: String? = null + override val displayAmount = DisplayAmount( + reserveBalance, + AmountType.Neutral + ) + override fun isCurrency(currency: String) = reserveBalance.currency == currency +} + +@JsonTypeName("withdrawn") +class WithdrawHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Exchange that was withdrawn from. + */ + val exchangeBaseUrl: String, + /** + * Unique identifier for the withdrawal session, can be used to + * query more detailed information from the wallet. + */ + val withdrawalGroupId: String, + val withdrawalSource: WithdrawalSource, + /** + * Amount that has been subtracted from the reserve's balance + * for this withdrawal. + */ + val amountWithdrawnRaw: Amount, + /** + * Amount that actually was added to the wallet's balance. + */ + val amountWithdrawnEffective: Amount +) : HistoryEvent(timestamp, eventId) { + override val detailPageLayout = R.layout.fragment_event_withdraw + override val title = cleanExchange(exchangeBaseUrl) + override val icon = R.drawable.transaction_withdrawal + override val showToUser = true + override val displayAmount = DisplayAmount( + amountWithdrawnEffective, + AmountType.Positive + ) + override fun isCurrency(currency: String) = amountWithdrawnRaw.currency == currency +} + +@JsonTypeName("order-accepted") +class OrderAcceptedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Condensed info about the order. + */ + val orderShortInfo: OrderShortInfo +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.ic_add_circle + override val title: String? = null + override fun isCurrency(currency: String) = orderShortInfo.amount.currency == currency +} + +@JsonTypeName("order-refused") +class OrderRefusedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Condensed info about the order. + */ + val orderShortInfo: OrderShortInfo +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.ic_cancel + override val title: String? = null + override fun isCurrency(currency: String) = orderShortInfo.amount.currency == currency +} + +@JsonTypeName("payment-sent") +class PaymentHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Condensed info about the order that we already paid for. + */ + val orderShortInfo: OrderShortInfo, + /** + * Set to true if the payment has been previously sent + * to the merchant successfully, possibly with a different session ID. + */ + val replay: Boolean, + /** + * Number of coins that were involved in the payment. + */ + val numCoins: Int, + /** + * Amount that was paid, including deposit and wire fees. + */ + val amountPaidWithFees: Amount, + /** + * Session ID that the payment was (re-)submitted under. + */ + val sessionId: String? +) : HistoryEvent(timestamp, eventId) { + override val detailPageLayout = R.layout.fragment_event_paid + override val title = orderShortInfo.summary + override val icon = R.drawable.ic_cash_usd_outline + override val showToUser = true + override val displayAmount = DisplayAmount( + amountPaidWithFees, + AmountType.Negative + ) + override fun isCurrency(currency: String) = orderShortInfo.amount.currency == currency +} + +@JsonTypeName("payment-aborted") +class PaymentAbortedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Condensed info about the order that we already paid for. + */ + val orderShortInfo: OrderShortInfo, + /** + * Amount that was lost due to refund and refreshing fees. + */ + val amountLost: Amount +) : HistoryEvent(timestamp, eventId) { + override val title = orderShortInfo.summary + override val icon = R.drawable.transaction_payment_aborted + override val showToUser = true + override val displayAmount = DisplayAmount( + amountLost, + AmountType.Negative + ) + override fun isCurrency(currency: String) = orderShortInfo.amount.currency == currency +} + +@JsonTypeName("refreshed") +class RefreshHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Amount that is now available again because it has + * been refreshed. + */ + val amountRefreshedEffective: Amount, + /** + * Amount that we spent for refreshing. + */ + val amountRefreshedRaw: Amount, + /** + * Why was the refreshing done? + */ + val refreshReason: RefreshReason, + val numInputCoins: Int, + val numRefreshedInputCoins: Int, + val numOutputCoins: Int, + /** + * Identifier for a refresh group, contains one or + * more refresh session IDs. + */ + val refreshGroupId: String +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.transaction_refresh + override val title: String? = null + override val showToUser = !(amountRefreshedRaw - amountRefreshedEffective).isZero() + override val displayAmount: DisplayAmount? + get() { + return if (showToUser) DisplayAmount( + amountRefreshedRaw - amountRefreshedEffective, + AmountType.Negative + ) + else null + } + + override fun isCurrency(currency: String) = amountRefreshedRaw.currency == currency +} + +@JsonTypeName("order-redirected") +class OrderRedirectedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Condensed info about the new order that contains a + * product (identified by the fulfillment URL) that we've already paid for. + */ + val newOrderShortInfo: OrderShortInfo, + /** + * Condensed info about the order that we already paid for. + */ + val alreadyPaidOrderShortInfo: OrderShortInfo +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.ic_directions + override val title = newOrderShortInfo.summary + override fun isCurrency(currency: String) = newOrderShortInfo.amount.currency == currency +} + +@JsonTypeName("tip-accepted") +class TipAcceptedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Unique identifier for the tip to query more information. + */ + val tipId: String, + /** + * Raw amount of the tip, without extra fees that apply. + */ + val tipRaw: Amount +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.transaction_tip_accepted + override val title: String? = null + override val showToUser = true + override val displayAmount = DisplayAmount( + tipRaw, + AmountType.Positive + ) + override fun isCurrency(currency: String) = tipRaw.currency == currency +} + +@JsonTypeName("tip-declined") +class TipDeclinedHistoryEvent( + timestamp: Timestamp, + eventId: String, + /** + * Unique identifier for the tip to query more information. + */ + val tipId: String, + /** + * Raw amount of the tip, without extra fees that apply. + */ + val tipAmount: Amount +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.transaction_tip_declined + override val title: String? = null + override val showToUser = true + override val displayAmount = DisplayAmount( + tipAmount, + AmountType.Neutral + ) + override fun isCurrency(currency: String) = tipAmount.currency == currency +} + +@JsonTypeName("refund") +class RefundHistoryEvent( + timestamp: Timestamp, + eventId: String, + val orderShortInfo: OrderShortInfo, + /** + * Unique identifier for this refund. + * (Identifies multiple refund permissions that were obtained at once.) + */ + val refundGroupId: String, + /** + * Part of the refund that couldn't be applied because + * the refund permissions were expired. + */ + val amountRefundedInvalid: Amount, + /** + * Amount that has been refunded by the merchant. + */ + val amountRefundedRaw: Amount, + /** + * Amount will be added to the wallet's balance after fees and refreshing. + */ + val amountRefundedEffective: Amount +) : HistoryEvent(timestamp, eventId) { + override val icon = R.drawable.transaction_refund + override val title = orderShortInfo.summary + override val detailPageLayout = R.layout.fragment_event_paid + override val showToUser = true + override val displayAmount = DisplayAmount( + amountRefundedEffective, + AmountType.Positive + ) + override fun isCurrency(currency: String) = amountRefundedRaw.currency == currency +} + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type" +) +@JsonSubTypes( + Type(value = WithdrawalSourceReserve::class, name = "reserve") +) +abstract class WithdrawalSource + +@Suppress("unused") +@JsonTypeName("tip") +class WithdrawalSourceTip( + val tipId: String +) : WithdrawalSource() + +@JsonTypeName("reserve") +class WithdrawalSourceReserve( + val reservePub: String +) : WithdrawalSource() + +data class OrderShortInfo( + /** + * Wallet-internal identifier of the proposal. + */ + val proposalId: String, + /** + * Order ID, uniquely identifies the order within a merchant instance. + */ + val orderId: String, + /** + * Base URL of the merchant. + */ + val merchantBaseUrl: String, + /** + * Amount that must be paid for the contract. + */ + val amount: Amount, + /** + * Summary of the proposal, given by the merchant. + */ + val summary: String +) diff --git a/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt new file mode 100644 index 0000000..31c2b93 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/JsonDialogFragment.kt @@ -0,0 +1,57 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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.wallet.history + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.fragment_json.* +import net.taler.wallet.R + +class JsonDialogFragment : DialogFragment() { + + companion object { + fun new(json: String): JsonDialogFragment { + return JsonDialogFragment().apply { + arguments = Bundle().apply { putString("json", json) } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_json, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val json = requireArguments().getString("json") + jsonView.text = json + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout(MATCH_PARENT, WRAP_CONTENT) + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt new file mode 100644 index 0000000..6c8fdaa --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/history/ReserveTransaction.kt @@ -0,0 +1,59 @@ +/* + * This file is part of GNU Taler + * (C) 2020 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.wallet.history + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME +import com.fasterxml.jackson.annotation.JsonTypeName +import net.taler.common.Timestamp + + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type" +) +@JsonSubTypes( + JsonSubTypes.Type(value = ReserveDepositTransaction::class, name = "DEPOSIT") +) +abstract class ReserveTransaction + + +@JsonTypeName("DEPOSIT") +class ReserveDepositTransaction( + /** + * Amount withdrawn. + */ + val amount: String, + /** + * Sender account payto://-URL + */ + @JsonProperty("sender_account_url") + val senderAccountUrl: String, + /** + * Transfer details uniquely identifying the transfer. + */ + @JsonProperty("wire_reference") + val wireReference: String, + /** + * Timestamp of the incoming wire transfer. + */ + val timestamp: Timestamp +) : ReserveTransaction() |