diff options
author | Torsten Grote <t@grobox.de> | 2019-12-27 09:56:42 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2019-12-27 09:56:42 -0300 |
commit | 89037988ca55cdd68e3be6294125b04ebfc50e77 (patch) | |
tree | e3ffefdece4adb423e834a2b84a2bca501543d51 /app | |
parent | b1ae959666e2bdbdc6020e373c95e887fda13eb7 (diff) | |
download | wallet-android-89037988ca55cdd68e3be6294125b04ebfc50e77.tar.gz wallet-android-89037988ca55cdd68e3be6294125b04ebfc50e77.tar.bz2 wallet-android-89037988ca55cdd68e3be6294125b04ebfc50e77.zip |
De-serialize first history events using Jackson
Diffstat (limited to 'app')
-rw-r--r-- | app/build.gradle | 2 | ||||
-rw-r--r-- | app/src/main/java/net/taler/wallet/WalletViewModel.kt | 8 | ||||
-rw-r--r-- | app/src/main/java/net/taler/wallet/history/HistoryEvent.kt | 154 | ||||
-rw-r--r-- | app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt | 42 | ||||
-rw-r--r-- | app/src/main/java/net/taler/wallet/history/WalletHistory.kt (renamed from app/src/main/java/net/taler/wallet/WalletHistory.kt) | 44 | ||||
-rw-r--r-- | app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt | 41 | ||||
-rw-r--r-- | app/src/main/res/navigation/nav_graph.xml | 2 | ||||
-rw-r--r-- | app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt | 172 | ||||
-rw-r--r-- | app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt | 36 |
9 files changed, 456 insertions, 45 deletions
diff --git a/app/build.gradle b/app/build.gradle index 5085681..7e0f2ce 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -62,5 +62,5 @@ dependencies { implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' // JSON parsing and serialization - implementation 'com.google.code.gson:gson:2.8.6' + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7" } diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt b/app/src/main/java/net/taler/wallet/WalletViewModel.kt index b933bf1..291321d 100644 --- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt +++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt @@ -20,8 +20,8 @@ import android.app.Application import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import com.google.android.material.snackbar.Snackbar import net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.history.HistoryEntry import org.json.JSONObject const val TAG = "taler-wallet" @@ -98,12 +98,6 @@ open class HistoryResult( val history: List<HistoryEntry> ) -open class HistoryEntry( - val detail: JSONObject, - val type: String, - val timestamp: JSONObject -) - open class PendingOperationInfo( val type: String, val detail: JSONObject diff --git a/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt b/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt new file mode 100644 index 0000000..be48ac9 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/history/HistoryEvent.kt @@ -0,0 +1,154 @@ +package net.taler.wallet.history + +import com.fasterxml.jackson.annotation.* +import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY +import com.fasterxml.jackson.annotation.JsonSubTypes.Type +import com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME +import org.json.JSONObject + +open class HistoryEntry( + val detail: JSONObject, + val type: String, + val timestamp: JSONObject +) + +enum class ReserveType { + /** + * Manually created. + */ + @JsonProperty("manual") + MANUAL, + /** + * Withdrawn from a bank that has "tight" Taler integration + */ + @JsonProperty("taler-bank-withdraw") + TALER_BANK_WITHDRAW, +} + +@JsonInclude(NON_EMPTY) +class ReserveCreationDetail(val type: ReserveType) + + +@JsonInclude(NON_EMPTY) +class Timestamp( + @JsonProperty("t_ms") + val ms: Long +) + +@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 +) + +class History: ArrayList<HistoryEvent>() + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type" +) +@JsonSubTypes( + Type(value = ExchangeAddedEvent::class, name = "exchange-added"), + Type(value = ExchangeUpdatedEvent::class, name = "exchange-updated"), + Type(value = ReserveBalanceUpdatedEvent::class, name = "reserve-balance-updated"), + Type(value = HistoryWithdrawnEvent::class, name = "withdrawn") +) +@JsonIgnoreProperties( + value = [ + "eventId" + ] +) +abstract class HistoryEvent( + val timestamp: Timestamp +) + + +@JsonTypeName("exchange-added") +class ExchangeAddedEvent( + timestamp: Timestamp, + val exchangeBaseUrl: String, + val builtIn: Boolean +) : HistoryEvent(timestamp) + +@JsonTypeName("exchange-updated") +class ExchangeUpdatedEvent( + timestamp: Timestamp, + val exchangeBaseUrl: String +) : HistoryEvent(timestamp) + + +@JsonTypeName("reserve-balance-updated") +class ReserveBalanceUpdatedEvent( + timestamp: Timestamp, + val newHistoryTransactions: List<ReserveTransaction>, + /** + * Condensed information about the reserve. + */ + val reserveShortInfo: ReserveShortInfo, + /** + * Amount currently left in the reserve. + */ + val amountReserveBalance: String, + /** + * Amount we expected to be in the reserve at that time, + * considering ongoing withdrawals from that reserve. + */ + val amountExpected: String +) : HistoryEvent(timestamp) + +@JsonTypeName("withdrawn") +class HistoryWithdrawnEvent( + timestamp: Timestamp, + /** + * 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 withdrawSessionId: String, + val withdrawalSource: WithdrawalSource, + /** + * Amount that has been subtracted from the reserve's balance + * for this withdrawal. + */ + val amountWithdrawnRaw: String, + /** + * Amount that actually was added to the wallet's balance. + */ + val amountWithdrawnEffective: String +) : HistoryEvent(timestamp) + + +@JsonTypeInfo( + use = NAME, + include = PROPERTY, + property = "type" +) +@JsonSubTypes( + Type(value = WithdrawalSourceReserve::class, name = "reserve") +) +abstract class WithdrawalSource + +@JsonTypeName("tip") +class WithdrawalSourceTip( + val tipId: String +) : WithdrawalSource() + +@JsonTypeName("reserve") +class WithdrawalSourceReserve( + val reservePub: String +) : WithdrawalSource() diff --git a/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt b/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt new file mode 100644 index 0000000..880639f --- /dev/null +++ b/app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt @@ -0,0 +1,42 @@ +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 + + +@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() diff --git a/app/src/main/java/net/taler/wallet/WalletHistory.kt b/app/src/main/java/net/taler/wallet/history/WalletHistory.kt index b9db0c1..89c6b3f 100644 --- a/app/src/main/java/net/taler/wallet/WalletHistory.kt +++ b/app/src/main/java/net/taler/wallet/history/WalletHistory.kt @@ -14,49 +14,19 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -package net.taler.wallet +package net.taler.wallet.history import android.os.Bundle import android.view.* import androidx.fragment.app.Fragment -import android.widget.TextView import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView - -class WalletHistoryAdapter(private var myDataset: HistoryResult) : RecyclerView.Adapter<WalletHistoryAdapter.MyViewHolder>() { - - init { - setHasStableIds(false) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { - val rowView = LayoutInflater.from(parent.context).inflate(R.layout.history_row, parent, false) - return MyViewHolder(rowView) - } - - override fun getItemCount(): Int { - return myDataset.history.size - } - - override fun onBindViewHolder(holder: MyViewHolder, position: Int) { - val h = myDataset.history[myDataset.history.size - position - 1] - val text = holder.rowView.findViewById<TextView>(R.id.history_text) - val subText = holder.rowView.findViewById<TextView>(R.id.history_subtext) - text.text = h.type - subText.text = h.timestamp.toString() + "\n" + h.detail.toString(1) - } - - fun update(updatedHistory: HistoryResult) { - this.myDataset = updatedHistory - this.notifyDataSetChanged() - } - - class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) -} - +import net.taler.wallet.HistoryResult +import net.taler.wallet.R +import net.taler.wallet.WalletViewModel /** * Wallet history. @@ -72,7 +42,9 @@ class WalletHistory : Fragment() { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - historyAdapter = WalletHistoryAdapter(HistoryResult(listOf())) + historyAdapter = WalletHistoryAdapter( + HistoryResult(listOf()) + ) model = activity?.run { ViewModelProviders.of(this)[WalletViewModel::class.java] @@ -96,7 +68,7 @@ class WalletHistory : Fragment() { private fun updateHistory() { model.getHistory { - if (it.history.size == 0) { + if (it.history.isEmpty()) { historyPlaceholder.visibility = View.VISIBLE } historyAdapter.update(it) diff --git a/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt b/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt new file mode 100644 index 0000000..46bca30 --- /dev/null +++ b/app/src/main/java/net/taler/wallet/history/WalletHistoryAdapter.kt @@ -0,0 +1,41 @@ +package net.taler.wallet.history + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import net.taler.wallet.HistoryResult +import net.taler.wallet.R + + +class WalletHistoryAdapter(private var myDataset: HistoryResult) : RecyclerView.Adapter<WalletHistoryAdapter.MyViewHolder>() { + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val rowView = LayoutInflater.from(parent.context).inflate(R.layout.history_row, parent, false) + return MyViewHolder(rowView) + } + + override fun getItemCount(): Int { + return myDataset.history.size + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val h = myDataset.history[myDataset.history.size - position - 1] + val text = holder.rowView.findViewById<TextView>(R.id.history_text) + val subText = holder.rowView.findViewById<TextView>(R.id.history_subtext) + text.text = h.type + subText.text = h.timestamp.toString() + "\n" + h.detail.toString(1) + } + + fun update(updatedHistory: HistoryResult) { + this.myDataset = updatedHistory + this.notifyDataSetChanged() + } + + class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) +} diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 88d64f7..f958b9c 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -43,7 +43,7 @@ tools:layout="@layout/fragment_settings" /> <fragment android:id="@+id/walletHistory" - android:name="net.taler.wallet.WalletHistory" + android:name="net.taler.wallet.history.WalletHistory" android:label="History" tools:layout="@layout/fragment_show_history" /> <fragment diff --git a/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt b/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt new file mode 100644 index 0000000..bd1ff9b --- /dev/null +++ b/app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt @@ -0,0 +1,172 @@ +package net.taler.wallet.history + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.readValue +import net.taler.wallet.history.ReserveType.MANUAL +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import kotlin.random.Random + +class HistoryEventTest { + + private val mapper = ObjectMapper().registerModule(KotlinModule()) + + private val timestamp = Random.nextLong() + private val exchangeBaseUrl = "https://exchange.test.taler.net/" + + @Test + fun `test ExchangeAddedEvent`() { + val builtIn = Random.nextBoolean() + val json = """{ + "type": "exchange-added", + "builtIn": $builtIn, + "eventId": "exchange-added;https%3A%2F%2Fexchange.test.taler.net%2F", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: ExchangeAddedEvent = mapper.readValue(json) + + assertEquals(builtIn, event.builtIn) + assertEquals(exchangeBaseUrl, event.exchangeBaseUrl) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test ExchangeUpdatedEvent`() { + val json = """{ + "type": "exchange-updated", + "eventId": "exchange-updated;https%3A%2F%2Fexchange.test.taler.net%2F", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + } + }""".trimIndent() + val event: ExchangeUpdatedEvent = mapper.readValue(json) + + assertEquals(exchangeBaseUrl, event.exchangeBaseUrl) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test ReserveShortInfo`() { + val json = """{ + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "reserveCreationDetail": { + "type": "manual" + }, + "reservePub": "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G" + }""".trimIndent() + val info: ReserveShortInfo = mapper.readValue(json) + + assertEquals(exchangeBaseUrl, info.exchangeBaseUrl) + assertEquals(MANUAL, info.reserveCreationDetail.type) + assertEquals("BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G", info.reservePub) + } + + @Test + fun `test ReserveBalanceUpdatedEvent`() { + val json = """{ + "type": "reserve-balance-updated", + "eventId": "reserve-balance-updated;K0H10Q6HB9WH0CKHQQMNH5C6GA7A9AR1E2XSS9G1KG3ZXMBVT26G", + "amountExpected": "TESTKUDOS:23", + "amountReserveBalance": "TESTKUDOS:10", + "timestamp": { + "t_ms": $timestamp + }, + "newHistoryTransactions": [ + { + "amount": "TESTKUDOS:10", + "sender_account_url": "payto:\/\/x-taler-bank\/bank.test.taler.net\/894", + "timestamp": { + "t_ms": $timestamp + }, + "wire_reference": "00000000004TR", + "type": "DEPOSIT" + } + ], + "reserveShortInfo": { + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "reserveCreationDetail": { + "type": "manual" + }, + "reservePub": "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G" + } + }""".trimIndent() + val event: ReserveBalanceUpdatedEvent = mapper.readValue(json) + + assertEquals(timestamp, event.timestamp.ms) + assertEquals("TESTKUDOS:23", event.amountExpected) + assertEquals("TESTKUDOS:10", event.amountReserveBalance) + assertEquals(1, event.newHistoryTransactions.size) + assertTrue(event.newHistoryTransactions[0] is ReserveDepositTransaction) + assertEquals(exchangeBaseUrl, event.reserveShortInfo.exchangeBaseUrl) + } + + @Test + fun `test HistoryWithdrawnEvent`() { + val json = """{ + "type": "withdrawn", + "withdrawSessionId": "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0", + "eventId": "withdrawn;974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0", + "amountWithdrawnEffective": "TESTKUDOS:9.8", + "amountWithdrawnRaw": "TESTKUDOS:10", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + }, + "withdrawalSource": { + "type": "reserve", + "reservePub": "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G" + } + }""".trimIndent() + val event: HistoryWithdrawnEvent = mapper.readValue(json) + + assertEquals( + "974FT7JDNR20EQKNR21G1HV9PB6T5AZHYHX9NHR51Q30ZK3T10S0", + event.withdrawSessionId + ) + assertEquals("TESTKUDOS:9.8", event.amountWithdrawnEffective) + assertEquals("TESTKUDOS:10", event.amountWithdrawnRaw) + assertTrue(event.withdrawalSource is WithdrawalSourceReserve) + assertEquals( + "BRT2P0YMQSD5F48V9XHVNH73ZTS6EZC0KCQCPGPZQWTSQB77615G", + (event.withdrawalSource as WithdrawalSourceReserve).reservePub + ) + assertEquals(exchangeBaseUrl, event.exchangeBaseUrl) + assertEquals(timestamp, event.timestamp.ms) + } + + @Test + fun `test list of events as History`() { + val builtIn = Random.nextBoolean() + val json = """[ + { + "type": "exchange-updated", + "eventId": "exchange-updated;https%3A%2F%2Fexchange.test.taler.net%2F", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + } + }, + { + "type": "exchange-added", + "builtIn": $builtIn, + "eventId": "exchange-added;https%3A%2F%2Fexchange.test.taler.net%2F", + "exchangeBaseUrl": "https:\/\/exchange.test.taler.net\/", + "timestamp": { + "t_ms": $timestamp + } + } + ]""".trimIndent() + val history: History = mapper.readValue(json) + + assertEquals(2, history.size) + assertTrue(history[0] is ExchangeUpdatedEvent) + assertTrue(history[1] is ExchangeAddedEvent) + } + +} diff --git a/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt b/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt new file mode 100644 index 0000000..b5687c6 --- /dev/null +++ b/app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt @@ -0,0 +1,36 @@ +package net.taler.wallet.history + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.readValue +import org.junit.Assert.assertEquals +import org.junit.Test +import kotlin.random.Random + +class ReserveTransactionTest { + + private val mapper = ObjectMapper().registerModule(KotlinModule()) + + private val timestamp = Random.nextLong() + + @Test + fun `test ExchangeAddedEvent`() { + val senderAccountUrl = "payto://x-taler-bank/bank.test.taler.net/894" + val json = """{ + "amount": "TESTKUDOS:10", + "sender_account_url": "payto:\/\/x-taler-bank\/bank.test.taler.net\/894", + "timestamp": { + "t_ms": $timestamp + }, + "wire_reference": "00000000004TR", + "type": "DEPOSIT" + }""".trimIndent() + val transaction: ReserveDepositTransaction = mapper.readValue(json) + + assertEquals("TESTKUDOS:10", transaction.amount) + assertEquals(senderAccountUrl, transaction.senderAccountUrl) + assertEquals("00000000004TR", transaction.wireReference) + assertEquals(timestamp, transaction.timestamp.ms) + } + +} |