summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2019-12-27 09:56:42 -0300
committerTorsten Grote <t@grobox.de>2019-12-27 09:56:42 -0300
commit89037988ca55cdd68e3be6294125b04ebfc50e77 (patch)
treee3ffefdece4adb423e834a2b84a2bca501543d51 /app
parentb1ae959666e2bdbdc6020e373c95e887fda13eb7 (diff)
downloadwallet-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.gradle2
-rw-r--r--app/src/main/java/net/taler/wallet/WalletViewModel.kt8
-rw-r--r--app/src/main/java/net/taler/wallet/history/HistoryEvent.kt154
-rw-r--r--app/src/main/java/net/taler/wallet/history/ReserveTransaction.kt42
-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.kt41
-rw-r--r--app/src/main/res/navigation/nav_graph.xml2
-rw-r--r--app/src/test/java/net/taler/wallet/history/HistoryEventTest.kt172
-rw-r--r--app/src/test/java/net/taler/wallet/history/ReserveTransactionTest.kt36
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)
+ }
+
+}