diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-09-02 10:50:45 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-09-02 10:50:45 +0200 |
commit | 26228306686bb37c18a48b25eba59c354379150c (patch) | |
tree | ef21e7cddcb48d2a1554579b9255026ae83de33f | |
parent | d783859b7928566090c88be05c6cf3871de6fbf2 (diff) | |
download | merchant-terminal-android-26228306686bb37c18a48b25eba59c354379150c.tar.gz merchant-terminal-android-26228306686bb37c18a48b25eba59c354379150c.tar.bz2 merchant-terminal-android-26228306686bb37c18a48b25eba59c354379150c.zip |
implement backend settings
-rw-r--r-- | app/build.gradle | 2 | ||||
-rw-r--r-- | app/src/main/java/net/taler/merchantpos/Amount.kt | 29 | ||||
-rw-r--r-- | app/src/main/java/net/taler/merchantpos/CreatePayment.kt | 9 | ||||
-rw-r--r-- | app/src/main/java/net/taler/merchantpos/MainActivity.kt | 10 | ||||
-rw-r--r-- | app/src/main/java/net/taler/merchantpos/MerchantConfig.kt | 7 | ||||
-rw-r--r-- | app/src/main/java/net/taler/merchantpos/MerchantHistory.kt | 138 | ||||
-rw-r--r-- | app/src/main/java/net/taler/merchantpos/MerchantSettings.kt | 111 | ||||
-rw-r--r-- | app/src/main/res/layout/fragment_create_payment.xml | 2 | ||||
-rw-r--r-- | app/src/main/res/layout/fragment_merchant_history.xml | 33 | ||||
-rw-r--r-- | app/src/main/res/layout/fragment_merchant_settings.xml | 93 | ||||
-rw-r--r-- | app/src/main/res/layout/history_row.xml | 79 |
11 files changed, 457 insertions, 56 deletions
diff --git a/app/build.gradle b/app/build.gradle index 0f36545..c1fc89b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,6 +64,8 @@ dependencies { implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + // JSON literals and parsing implementation 'com.beust:klaxon:5.0.11' } diff --git a/app/src/main/java/net/taler/merchantpos/Amount.kt b/app/src/main/java/net/taler/merchantpos/Amount.kt new file mode 100644 index 0000000..15cadf4 --- /dev/null +++ b/app/src/main/java/net/taler/merchantpos/Amount.kt @@ -0,0 +1,29 @@ +package net.taler.merchantpos + +import org.json.JSONObject + +data class Amount(val currency: String, val amount: String) { + fun isZero(): Boolean { + return amount.toDouble() == 0.0 + } + + companion object { + const val FRACTIONAL_BASE = 1e8; + fun fromJson(jsonAmount: JSONObject): Amount { + val amountCurrency = jsonAmount.getString("currency") + val amountValue = jsonAmount.getString("value") + val amountFraction = jsonAmount.getString("fraction") + val amountIntValue = Integer.parseInt(amountValue) + val amountIntFraction = Integer.parseInt(amountFraction) + return Amount( + amountCurrency, + (amountIntValue + amountIntFraction / FRACTIONAL_BASE).toString() + ) + } + + fun fromString(strAmount: String): Amount { + val components = strAmount.split(":") + return Amount(components[0], components[1]) + } + } +}
\ No newline at end of file diff --git a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt index c6a8066..83dbed8 100644 --- a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt +++ b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt @@ -1,11 +1,13 @@ package net.taler.merchantpos +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.EditText +import android.widget.TextView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import androidx.navigation.fragment.findNavController @@ -36,6 +38,10 @@ class CreatePayment : Fragment() { override fun onResume() { super.onResume() this.paused = false + + val textView = view!!.findViewById<TextView>(R.id.text_create_payment_amount_label) + @SuppressLint("SetTextI18n") + textView.text = "Amount (${model.merchantConfig!!.currency})" } override fun onCreate(savedInstanceState: Bundle?) { @@ -50,7 +56,7 @@ class CreatePayment : Fragment() { private fun onRequestPayment() { val amountValStr = activity!!.findViewById<EditText>(R.id.edit_payment_amount).text - val amount = "TESTKUDOS:${amountValStr}" + val amount = "${model.merchantConfig!!.currency}:${amountValStr}" model.activeAmount = amount model.activeSubject = activity!!.findViewById<EditText>(R.id.edit_payment_subject).text @@ -117,6 +123,7 @@ class CreatePayment : Fragment() { requestPaymentButton.setOnClickListener { onRequestPayment() } + return view } diff --git a/app/src/main/java/net/taler/merchantpos/MainActivity.kt b/app/src/main/java/net/taler/merchantpos/MainActivity.kt index e8e9a2a..7c8330a 100644 --- a/app/src/main/java/net/taler/merchantpos/MainActivity.kt +++ b/app/src/main/java/net/taler/merchantpos/MainActivity.kt @@ -1,5 +1,6 @@ package net.taler.merchantpos +import android.content.Context import android.nfc.NfcAdapter import android.nfc.Tag import android.nfc.tech.IsoDep @@ -276,9 +277,16 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte findViewById<Toolbar>(R.id.toolbar) .setupWithNavController(navController, appBarConfiguration) + val prefs = getSharedPreferences("taler-merchant-terminal", Context.MODE_PRIVATE) + + val baseUrl = prefs.getString("merchantBackendUrl", "https://backend.test.taler.net") + val instance = prefs.getString("merchantBackendInstance", "default") + val apiKey = prefs.getString("merchantBackendApiKey", "sandbox") + val currency = prefs.getString("merchantBackendCurrency", "TESTKUDOS") + model = ViewModelProviders.of(this)[PosTerminalViewModel::class.java] model.merchantConfig = - MerchantConfig("https://backend.test.taler.net", "default", "sandbox") + MerchantConfig(baseUrl!!, instance!!, apiKey!!, currency!!) } diff --git a/app/src/main/java/net/taler/merchantpos/MerchantConfig.kt b/app/src/main/java/net/taler/merchantpos/MerchantConfig.kt index 3e043a0..626d60b 100644 --- a/app/src/main/java/net/taler/merchantpos/MerchantConfig.kt +++ b/app/src/main/java/net/taler/merchantpos/MerchantConfig.kt @@ -2,7 +2,12 @@ package net.taler.merchantpos import android.net.Uri -data class MerchantConfig(val baseUrl: String, val instance: String, val apiKey: String) { +data class MerchantConfig( + val baseUrl: String, + val instance: String, + val apiKey: String, + val currency: String +) { fun urlFor(endpoint: String, params: Map<String, String>?): String { val uriBuilder = Uri.parse(baseUrl).buildUpon() uriBuilder.appendPath(endpoint) diff --git a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt index c3bab06..b79f836 100644 --- a/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt +++ b/app/src/main/java/net/taler/merchantpos/MerchantHistory.kt @@ -1,23 +1,48 @@ package net.taler.merchantpos +import android.annotation.SuppressLint import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.android.volley.Request import com.android.volley.RequestQueue +import com.android.volley.Response +import com.android.volley.VolleyError import com.android.volley.toolbox.Volley +import com.google.android.material.snackbar.Snackbar +import org.json.JSONObject +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.* -class MyAdapter(private val myDataset: Array<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { + + +data class HistoryItem( + val orderId: String, + val amount: Amount, + val summary: String, + val timestamp: Instant +) + +class MyAdapter(private var myDataset: List<HistoryItem>) : + RecyclerView.Adapter<MyAdapter.MyViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { - val textView = LayoutInflater.from(parent.context).inflate(R.layout.history_row, parent, false) - return MyViewHolder(textView as TextView) + val view = + LayoutInflater.from(parent.context).inflate(R.layout.history_row, parent, false) + return MyViewHolder(view) } override fun getItemCount(): Int { @@ -25,24 +50,47 @@ class MyAdapter(private val myDataset: Array<String>) : RecyclerView.Adapter<MyA } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { - holder.textView.text = myDataset[position] + val item = myDataset[position] + val summaryTextView = holder.rowView.findViewById<TextView>(R.id.text_history_summary) + summaryTextView.text = myDataset[position].summary + + val amount = myDataset[position].amount + val amountTextView = holder.rowView.findViewById<TextView>(R.id.text_history_amount) + @SuppressLint("SetTextI18n") + amountTextView.text = "${amount.amount} ${amount.currency}" + + val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) + .withLocale(Locale.UK) + .withZone(ZoneId.systemDefault()) + val timestampTextView = holder.rowView.findViewById<TextView>(R.id.text_history_time) + timestampTextView.text = formatter.format(item.timestamp) + + val orderIdTextView = holder.rowView.findViewById<TextView>(R.id.text_history_order_id) + orderIdTextView.text = item.orderId + } + + fun setData(dataset: List<HistoryItem>) { + this.myDataset = dataset + this.notifyDataSetChanged() } - class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView) + class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) +} + +fun parseTalerTimestamp(s: String): Instant { + return Instant.ofEpochSecond(s.substringAfterLast('(').substringBeforeLast(')').toLong()) } /** - * A simple [Fragment] subclass. - * Activities that contain this fragment must implement the - * [MerchantHistory.OnFragmentInteractionListener] interface - * to handle interaction events. - * Use the [MerchantHistory.newInstance] factory method to - * create an instance of this fragment. - * + * Fragment to display the merchant's payment history, + * received from the backend. */ class MerchantHistory : Fragment() { private lateinit var queue: RequestQueue private lateinit var model: PosTerminalViewModel + private val historyListAdapter = MyAdapter(listOf()) + + private val isLoading = MutableLiveData<Boolean>().apply { value = false } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -54,6 +102,48 @@ class MerchantHistory : Fragment() { queue = Volley.newRequestQueue(context) } + private fun onNetworkError(volleyError: VolleyError?) { + this.isLoading.value = false + val mySnackbar = Snackbar.make(view!!, "Network Error", Snackbar.LENGTH_SHORT) + mySnackbar.show() + } + + private fun onHistoryResponse(body: JSONObject) { + this.isLoading.value = false + Log.v(TAG, "got history response ${body}") + val data = arrayListOf<HistoryItem>() + val historyJson = body.getJSONArray("history") + for (i in 0 until historyJson.length()) { + val item = historyJson.getJSONObject(i) + val orderId = item.getString("order_id") + val summary = item.getString("summary") + val timestampStr = item.getString("timestamp") + val timestamp = parseTalerTimestamp(timestampStr) + val amount = Amount.fromString(item.getString("amount")) + data.add(HistoryItem(orderId, amount, summary, timestamp)) + } + historyListAdapter.setData(data) + } + + private fun fetchHistory() { + isLoading.value = true + val instance = model.merchantConfig!!.instance + val req = MerchantInternalRequest( + Request.Method.GET, + model.merchantConfig!!, + "history", + mapOf("instance" to instance), + null, + Response.Listener { onHistoryResponse(it) }, + Response.ErrorListener { onNetworkError(it) }) + queue.add(req) + } + + override fun onResume() { + super.onResume() + fetchHistory() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -62,16 +152,28 @@ class MerchantHistory : Fragment() { val myItemDecoration = DividerItemDecoration(context, myLayoutManager.orientation) // Inflate the layout for this fragment val view = inflater.inflate(R.layout.fragment_merchant_history, container, false) - val myList = ArrayList<String>() - for (i in 0..100) { - myList.add("Element $i") - } - val myArray: Array<String> = myList.toTypedArray() view.findViewById<RecyclerView>(R.id.list_history).apply { layoutManager = myLayoutManager - adapter = MyAdapter(myArray) + adapter = historyListAdapter addItemDecoration(myItemDecoration) } + + val refreshLayout = view.findViewById<SwipeRefreshLayout>(R.id.swiperefresh) + refreshLayout.isRefreshing = false + refreshLayout.setOnRefreshListener { + Log.v(TAG, "refreshing!") + fetchHistory() + } + + this.isLoading.observe(this, androidx.lifecycle.Observer { loading -> + Log.v(TAG, "setting refreshing to ${loading}") + refreshLayout.isRefreshing = loading + }) + return view } + + companion object { + val TAG = "taler-merchant" + } } diff --git a/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt b/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt index 1903bce..7797dcb 100644 --- a/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt +++ b/app/src/main/java/net/taler/merchantpos/MerchantSettings.kt @@ -1,21 +1,128 @@ package net.taler.merchantpos +import android.content.Context import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import android.widget.TextView +import androidx.lifecycle.ViewModelProviders +import com.android.volley.Request +import com.android.volley.RequestQueue +import com.android.volley.Response +import com.android.volley.VolleyError +import com.android.volley.toolbox.Volley +import com.google.android.material.snackbar.Snackbar +import org.json.JSONObject /** * Fragment that displays merchant settings. */ class MerchantSettings : Fragment() { + + private lateinit var queue: RequestQueue + private lateinit var model: PosTerminalViewModel + + private var newConfig: MerchantConfig? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + model = activity?.run { + ViewModelProviders.of(this)[PosTerminalViewModel::class.java] + } ?: throw Exception("Invalid Activity") + + queue = Volley.newRequestQueue(context) + } + + private fun reset(view: View) { + val backendUrlEdit = view.findViewById<EditText>(R.id.edit_settings_backend_url) + backendUrlEdit.setText(model.merchantConfig!!.baseUrl, TextView.BufferType.EDITABLE) + + val backendInstanceEdit = view.findViewById<EditText>(R.id.edit_settings_instance) + backendInstanceEdit.setText(model.merchantConfig!!.instance, TextView.BufferType.EDITABLE) + + val backendApiKeyEdit = view.findViewById<EditText>(R.id.edit_settings_apikey) + backendApiKeyEdit.setText(model.merchantConfig!!.apiKey, TextView.BufferType.EDITABLE) + + val currencyView = view.findViewById<TextView>(R.id.text_settings_currency) + currencyView.text = model.merchantConfig!!.currency + } + + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_merchant_settings, container, false) + val view = inflater.inflate(R.layout.fragment_merchant_settings, container, false) + + reset(view) + + val buttonApply = view.findViewById<Button>(R.id.button_settings_apply) + buttonApply.setOnClickListener { + + val backendUrlEdit = view.findViewById<EditText>(R.id.edit_settings_backend_url) + val backendInstanceEdit = view.findViewById<EditText>(R.id.edit_settings_instance) + val backendApiKeyEdit = view.findViewById<EditText>(R.id.edit_settings_apikey) + + val config = MerchantConfig( + backendUrlEdit.text.toString(), + backendInstanceEdit.text.toString(), + backendApiKeyEdit.text.toString(), + "UNKNOWN" + ) + + newConfig = config + + val req = MerchantInternalRequest( + Request.Method.GET, + config, + "config", + mapOf("instance" to config.instance), + null, + Response.Listener { onConfigReceived(it) }, + Response.ErrorListener { onNetworkError(it) }) + + queue.add(req) + + } + + val buttonReset = view.findViewById<Button>(R.id.button_settings_reset) + buttonReset.setOnClickListener { + reset(view) + } + + return view + } + + private fun onConfigReceived(it: JSONObject) { + val currency = it.getString("currency") + val mySnackbar = + Snackbar.make(view!!, "Changed to new ${currency} merchant", Snackbar.LENGTH_SHORT) + + val config = this.newConfig!!.copy(currency = currency) + this.newConfig = null + model.merchantConfig = config + + val currencyView = view!!.findViewById<TextView>(R.id.text_settings_currency) + currencyView.text = currency + + mySnackbar.show() + + val prefs = activity!!.getSharedPreferences("taler-merchant-terminal", Context.MODE_PRIVATE) + prefs.edit().putString("merchantBackendUrl", config.baseUrl) + .putString("merchantBackendInstance", config.instance) + .putString("merchantBackendApiKey", config.apiKey) + .putString("merchantBackendCurrency", config.currency) + } + + private fun onNetworkError(it: VolleyError) { + val mySnackbar = + Snackbar.make(view!!, "Error: Invalid Configuration", Snackbar.LENGTH_SHORT) + mySnackbar.show() } } diff --git a/app/src/main/res/layout/fragment_create_payment.xml b/app/src/main/res/layout/fragment_create_payment.xml index f72f263..c3f2e5f 100644 --- a/app/src/main/res/layout/fragment_create_payment.xml +++ b/app/src/main/res/layout/fragment_create_payment.xml @@ -36,7 +36,7 @@ android:text="Amount (TESTKUDOS)" android:layout_width="match_parent" android:layout_height="wrap_content" - android:id="@+id/textView6"/> + android:id="@+id/text_create_payment_amount_label"/> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/fragment_merchant_history.xml b/app/src/main/res/layout/fragment_merchant_history.xml index 92abdf7..abb757c 100644 --- a/app/src/main/res/layout/fragment_merchant_history.xml +++ b/app/src/main/res/layout/fragment_merchant_history.xml @@ -1,32 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.core.widget.NestedScrollView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" +<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/swiperefresh" android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_margin="15dp"> + android:layout_height="match_parent"> - <LinearLayout + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_history" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> + android:scrollbars="vertical" /> - <View - android:id="@+id/header" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list_history" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:scrollbars="vertical" - android:layout_marginTop="47dp" android:layout_marginEnd="75dp" - android:layout_marginBottom="17dp"/> - - <Button - android:text="Button" - android:layout_width="match_parent" - android:layout_height="wrap_content" android:id="@+id/button2"/> - </LinearLayout> -</androidx.core.widget.NestedScrollView> +</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> diff --git a/app/src/main/res/layout/fragment_merchant_settings.xml b/app/src/main/res/layout/fragment_merchant_settings.xml index 8fcf233..b6e3707 100644 --- a/app/src/main/res/layout/fragment_merchant_settings.xml +++ b/app/src/main/res/layout/fragment_merchant_settings.xml @@ -11,5 +11,98 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Merchant Backend Base URL" /> + + <EditText + android:id="@+id/edit_settings_backend_url" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" + android:text="Name" /> + + <Space + android:layout_width="match_parent" + android:layout_height="40dp" /> + + <TextView + android:id="@+id/textView4" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Merchant Instance" /> + + <EditText + android:id="@+id/edit_settings_instance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" + android:text="Name" /> + + <Space + android:layout_width="match_parent" + android:layout_height="40dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="API Key" /> + + <EditText + android:id="@+id/edit_settings_apikey" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="textPassword" /> + + <Space + android:layout_width="match_parent" + android:layout_height="40dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Currency" /> + + <TextView + android:id="@+id/text_settings_currency" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="TextView" + android:textAppearance="@style/TextAppearance.AppCompat.Display1" /> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/button_settings_reset" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:text="Reset" /> + + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + + <Button + android:id="@+id/button_settings_apply" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|right" + android:text="Apply" /> + </LinearLayout> </LinearLayout> + </FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/history_row.xml b/app/src/main/res/layout/history_row.xml index a67cf37..12b93d5 100644 --- a/app/src/main/res/layout/history_row.xml +++ b/app/src/main/res/layout/history_row.xml @@ -1,7 +1,74 @@ <?xml version="1.0" encoding="utf-8"?> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/historyText" - android:textSize="20sp"> -</TextView>
\ No newline at end of file +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_marginHorizontal="15dp" + android:layout_marginVertical="5dp"> + + <TextView + android:id="@+id/text_history_summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="24sp" + android:textStyle="bold" + tools:text="One Cappuccino" /> + + <TextView + android:id="@+id/text_history_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="1 Euro" + android:textSize="20sp" + android:textStyle="bold" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Received at " + android:textAllCaps="false" + android:textSize="20sp" + android:textStyle="italic" /> + + + <TextView + android:id="@+id/text_history_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="2019-08-31 14:25" + android:textSize="20sp" /> + + </LinearLayout> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Ref. No: " + android:textAllCaps="false" + android:textSize="20sp" + android:textStyle="italic" /> + + <TextView + android:id="@+id/text_history_order_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="2019.242-014B6QPFMHCTY" + android:textAllCaps="false" + android:textSize="20sp" + android:textStyle="italic" /> + </LinearLayout> + +</LinearLayout> |