diff options
author | Florian Dold <florian.dold@gmail.com> | 2019-12-03 14:42:32 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2019-12-03 14:42:32 +0100 |
commit | 80cd3c9d6d83798316a1222f3875d3a0e74ca278 (patch) | |
tree | 77bb114b1c47199d41d788c1e968c692eb07a727 /app | |
parent | 87c1b63c4cf2b81963735feb0bce8b8f0b004dba (diff) | |
download | wallet-android-80cd3c9d6d83798316a1222f3875d3a0e74ca278.tar.gz wallet-android-80cd3c9d6d83798316a1222f3875d3a0e74ca278.tar.bz2 wallet-android-80cd3c9d6d83798316a1222f3875d3a0e74ca278.zip |
UI tweaks
Diffstat (limited to 'app')
17 files changed, 506 insertions, 72 deletions
diff --git a/app/build.gradle b/app/build.gradle index e49c3d8..8270882 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 - buildToolsVersion "29.0.1" + buildToolsVersion "29.0.2" defaultConfig { applicationId "net.taler.wallet" minSdkVersion 18 diff --git a/app/src/main/java/net/taler/wallet/MainActivity.kt b/app/src/main/java/net/taler/wallet/MainActivity.kt index 3f19986..fa0b1d9 100644 --- a/app/src/main/java/net/taler/wallet/MainActivity.kt +++ b/app/src/main/java/net/taler/wallet/MainActivity.kt @@ -127,8 +127,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the menu; this adds items to the action bar if it is present. - menuInflater.inflate(R.menu.main, menu) - return true + //menuInflater.inflate(R.menu.main, menu) + return false } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/net/taler/wallet/ShowBalance.kt b/app/src/main/java/net/taler/wallet/ShowBalance.kt index a45d4fa..ce07816 100644 --- a/app/src/main/java/net/taler/wallet/ShowBalance.kt +++ b/app/src/main/java/net/taler/wallet/ShowBalance.kt @@ -3,10 +3,8 @@ package net.taler.wallet import android.os.Bundle import android.util.Log +import android.view.* import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import android.widget.Button import android.widget.TextView import androidx.lifecycle.Observer @@ -17,7 +15,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.zxing.integration.android.IntentIntegrator import me.zhanghai.android.materialprogressbar.MaterialProgressBar -class MyAdapter(private var myDataset: WalletBalances) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { +class WalletBalanceAdapter(private var myDataset: WalletBalances) : RecyclerView.Adapter<WalletBalanceAdapter.MyViewHolder>() { init { setHasStableIds(false) @@ -59,6 +57,35 @@ class MyAdapter(private var myDataset: WalletBalances) : RecyclerView.Adapter<My class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) } +class PendingOperationsAdapter(private var myDataset: PendingOperations) : RecyclerView.Adapter<PendingOperationsAdapter.MyViewHolder>() { + + init { + setHasStableIds(false) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + val rowView = LayoutInflater.from(parent.context).inflate(R.layout.pending_row, parent, false) + return MyViewHolder(rowView) + } + + override fun getItemCount(): Int { + return myDataset.pending.size + } + + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + val p = myDataset.pending[position] + val textView = holder.rowView.findViewById<TextView>(R.id.pending_text) + textView.text = p.type + } + + fun update(updatedDataset: PendingOperations) { + this.myDataset = updatedDataset + this.notifyDataSetChanged() + } + + class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) +} + /** * A simple [Fragment] subclass. @@ -66,12 +93,15 @@ class MyAdapter(private var myDataset: WalletBalances) : RecyclerView.Adapter<My */ class ShowBalance : Fragment() { + private lateinit var pendingOperationsLabel: View lateinit var balancesView: RecyclerView lateinit var balancesPlaceholderView: TextView lateinit var model: WalletViewModel - lateinit var balancesAdapter: MyAdapter + lateinit var balancesAdapter: WalletBalanceAdapter + + lateinit var pendingAdapter: PendingOperationsAdapter - fun triggerLoading() { + private fun triggerLoading() { val loading: Boolean = (model.testWithdrawalInProgress.value == true) || (model.balances.value == null) || !model.balances.value!!.initialized @@ -90,8 +120,29 @@ class ShowBalance : Fragment() { Log.v("taler-wallet", "called onResume on ShowBalance") } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.retry_pending -> { + model.retryPendingNow() + true + } + R.id.reload_balance -> { + triggerLoading() + model.balances.value = WalletBalances(false, listOf()) + model.getBalances() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + activity?.menuInflater?.inflate(R.menu.balance, menu) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setHasOptionsMenu(true) model = activity?.run { ViewModelProviders.of(this)[WalletViewModel::class.java] @@ -113,7 +164,15 @@ class ShowBalance : Fragment() { } Log.v(TAG, "updating balances ${balances}") balancesAdapter.update(balances) - //this.balancesView.adapter = balancesAdapter + } + + private fun updatePending(pendingOperations: PendingOperations) { + if (pendingOperations.pending.size == 0) { + pendingOperationsLabel.visibility = View.GONE + } else { + pendingOperationsLabel.visibility = View.VISIBLE + } + pendingAdapter.update(pendingOperations) } override fun onCreateView( @@ -137,14 +196,14 @@ class ShowBalance : Fragment() { this.balancesView = view.findViewById(R.id.list_balances) this.balancesPlaceholderView = view.findViewById(R.id.list_balances_placeholder) - val myLayoutManager = LinearLayoutManager(context) - val myItemDecoration = DividerItemDecoration(context, myLayoutManager.orientation) val balances = model.balances.value!! - balancesAdapter = MyAdapter(balances) + balancesAdapter = WalletBalanceAdapter(balances) view.findViewById<RecyclerView>(R.id.list_balances).apply { + val myLayoutManager = LinearLayoutManager(context) + val myItemDecoration = DividerItemDecoration(context, myLayoutManager.orientation) layoutManager = myLayoutManager adapter = balancesAdapter addItemDecoration(myItemDecoration) @@ -163,6 +222,22 @@ class ShowBalance : Fragment() { triggerLoading() }) + pendingAdapter = PendingOperationsAdapter(PendingOperations(listOf())) + + this.pendingOperationsLabel = view.findViewById<View>(R.id.pending_operations_label) + + view.findViewById<RecyclerView>(R.id.list_pending).apply { + val myLayoutManager = LinearLayoutManager(context) + val myItemDecoration = DividerItemDecoration(context, myLayoutManager.orientation) + layoutManager = myLayoutManager + adapter = pendingAdapter + addItemDecoration(myItemDecoration) + } + + model.pendingOperations.observe(this, Observer { + updatePending(it) + }) + return view } } diff --git a/app/src/main/java/net/taler/wallet/WalletHistory.kt b/app/src/main/java/net/taler/wallet/WalletHistory.kt index baf825e..6c22ad1 100644 --- a/app/src/main/java/net/taler/wallet/WalletHistory.kt +++ b/app/src/main/java/net/taler/wallet/WalletHistory.kt @@ -2,30 +2,111 @@ package net.taler.wallet import android.os.Bundle +import android.view.* import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +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 text = holder.rowView.findViewById<TextView>(R.id.history_text) + text.text = myDataset.history[position].type + } + + fun update(updatedHistory: HistoryResult) { + this.myDataset = updatedHistory + this.notifyDataSetChanged() + } + + class MyViewHolder(val rowView: View) : RecyclerView.ViewHolder(rowView) +} -// TODO: Rename parameter arguments, choose names that match -// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER -private const val ARG_PARAM1 = "param1" -private const val ARG_PARAM2 = "param2" /** - * A simple [Fragment] subclass. + * Wallet history. * */ class WalletHistory : Fragment() { + lateinit var model: WalletViewModel + lateinit var historyAdapter: WalletHistoryAdapter + lateinit var historyPlaceholder: View + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + + historyAdapter = WalletHistoryAdapter(HistoryResult(listOf())) + + model = activity?.run { + ViewModelProviders.of(this)[WalletViewModel::class.java] + } ?: throw Exception("Invalid Activity") + + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + activity?.menuInflater?.inflate(R.menu.history, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.reload_history -> { + updateHistory() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun updateHistory() { + model.getHistory { + if (it.history.size == 0) { + historyPlaceholder.visibility = View.VISIBLE + } + historyAdapter.update(it) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_show_history, container, false) - } + val view = inflater.inflate(R.layout.fragment_show_history, container, false) + val myLayoutManager = LinearLayoutManager(context) + val myItemDecoration = DividerItemDecoration(context, myLayoutManager.orientation) + view.findViewById<RecyclerView>(R.id.list_history).apply { + layoutManager = myLayoutManager + adapter = historyAdapter + addItemDecoration(myItemDecoration) + } + + historyPlaceholder = view.findViewById<View>(R.id.list_history_placeholder) + historyPlaceholder.visibility = View.GONE + updateHistory() + return view + } + + companion object { + const val TAG = "taler-wallet" + } } diff --git a/app/src/main/java/net/taler/wallet/WalletViewModel.kt b/app/src/main/java/net/taler/wallet/WalletViewModel.kt index 34e8eed..dffc4a0 100644 --- a/app/src/main/java/net/taler/wallet/WalletViewModel.kt +++ b/app/src/main/java/net/taler/wallet/WalletViewModel.kt @@ -48,7 +48,7 @@ open class PayStatus { class Loading : PayStatus() data class Prepared( val contractTerms: ContractTerms, - val proposalId: Int, + val proposalId: String, val totalFees: Amount ) : PayStatus() @@ -71,6 +71,24 @@ open class WithdrawStatus { data class Withdrawing(val talerWithdrawUri: String) : WithdrawStatus() } +open class HistoryResult( + val history: List<HistoryEntry> +) + +open class HistoryEntry( + val detail: JSONObject, + val type: String +) + +open class PendingOperationInfo( + val type: String, + val detail: JSONObject +) + +open class PendingOperations( + val pending: List<PendingOperationInfo> +) + class WalletViewModel(val app: Application) : AndroidViewModel(app) { private var initialized = false @@ -91,6 +109,10 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { value = WithdrawStatus.None() } + val pendingOperations = MutableLiveData<PendingOperations>().apply { + value = PendingOperations(listOf()) + } + private val walletBackendApi = WalletBackendApi(app) fun init() { @@ -101,9 +123,13 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { this.initialized = true + getBalances() + getPending() + walletBackendApi.notificationHandler = { Log.i(TAG, "got notification from wallet") getBalances() + getPending() } } @@ -126,6 +152,37 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { } } + private fun getPending() { + walletBackendApi.sendRequest("getPendingOperations", null) { result -> + Log.i(TAG, "got getPending result") + val pendingList = mutableListOf<PendingOperationInfo>() + val pendingJson = result.getJSONArray("pendingOperations") + for (i in 0 until pendingJson.length()) { + val p = pendingJson.getJSONObject(i) + val type = p.getString("type") + pendingList.add(PendingOperationInfo(type, p)) + } + Log.i(TAG, "Got ${pendingList.size} pending operations") + pendingOperations.postValue(PendingOperations((pendingList))) + } + } + + fun getHistory(cb: (r: HistoryResult) -> Unit) { + walletBackendApi.sendRequest("getHistory", null) { result -> + val historyEntries = mutableListOf<HistoryEntry>() + val historyList = result.getJSONArray("history") + for (i in 0 until historyList.length()) { + val h = historyList.getJSONObject(i) + Log.v(TAG, "got history entry $h") + val type = h.getString("type") + Log.v(TAG, "got history entry type $type") + val detail = h.getJSONObject("detail") + historyEntries.add(HistoryEntry(detail, type)) + } + cb(HistoryResult(historyEntries)) + } + } + fun withdrawTestkudos() { testWithdrawalInProgress.value = true @@ -144,10 +201,10 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { Log.v(TAG, "got preparePay result") val status = result.getString("status") var contractTerms: ContractTerms? = null - var proposalId: Int? = null + var proposalId: String? = null var totalFees: Amount? = null if (result.has("proposalId")) { - proposalId = result.getInt("proposalId") + proposalId = result.getString("proposalId") } if (result.has("contractTerms")) { val ctJson = result.getJSONObject("contractTerms") @@ -175,7 +232,7 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { } } - fun confirmPay(proposalId: Int) { + fun confirmPay(proposalId: String) { val msg = JSONObject() msg.put("operation", "confirmPay") @@ -191,7 +248,6 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { walletBackendApi.sendRequest("reset", null) testWithdrawalInProgress.value = false balances.value = WalletBalances(false, listOf()) - getBalances() } fun startTunnel() { @@ -249,6 +305,10 @@ class WalletViewModel(val app: Application) : AndroidViewModel(app) { } } + fun retryPendingNow() { + walletBackendApi.sendRequest("retryPendingNow", null) + } + override fun onCleared() { walletBackendApi.destroy() super.onCleared() diff --git a/app/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/app/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt index 45c719d..31e0f08 100644 --- a/app/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt +++ b/app/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt @@ -43,6 +43,8 @@ class WalletBackendApi(private val app: Application) { when (msg.what) { WalletBackendService.MSG_REPLY -> { val requestID = msg.data.getInt("requestID", 0) + val operation = msg.data.getString("operation", "??") + Log.i(TAG, "got reply for operation $operation ($requestID)") val h = api.handlers.get(requestID) if (h == null) { Log.e(TAG, "request ID not associated with a handler") @@ -87,8 +89,8 @@ class WalletBackendApi(private val app: Application) { args: JSONObject?, onResponse: (message: JSONObject) -> Unit = { } ) { - Log.i(TAG, "sending request for operation $operation") val requestID = nextRequestID++ + Log.i(TAG, "sending request for operation $operation ($requestID)") val msg = Message.obtain(null, WalletBackendService.MSG_COMMAND) handlers.put(requestID, onResponse) msg.replyTo = incomingMessenger diff --git a/app/src/main/java/net/taler/wallet/backend/WalletBackendService.kt b/app/src/main/java/net/taler/wallet/backend/WalletBackendService.kt index 916dfdb..b72ce6b 100644 --- a/app/src/main/java/net/taler/wallet/backend/WalletBackendService.kt +++ b/app/src/main/java/net/taler/wallet/backend/WalletBackendService.kt @@ -14,11 +14,13 @@ import net.taler.wallet.HostCardEmulatorService import org.json.JSONObject import java.io.File import java.io.InputStream +import java.lang.Process import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.ConcurrentHashMap +import kotlin.system.exitProcess -val TAG = "taler-wallet-backend" +private const val TAG = "taler-wallet-backend" /** * Module loader to handle module loading requests from the wallet-core running on node/v8. @@ -110,17 +112,7 @@ private class AssetModuleLoader( private class AssetDataHandler(private val assetManager: AssetManager) : AkonoJni.GetDataHandler { override fun handleGetData(what: String): ByteArray? { - if (what == "taler-emscripten-lib.wasm") { - Log.i(TAG, "loading emscripten binary from taler-wallet") - val stream = - assetManager.open("node_modules/taler-wallet/emscripten/taler-emscripten-lib.wasm") - val bytes: ByteArray = stream.readBytes() - Log.i(TAG, "size of emscripten binary: ${bytes.size}") - return bytes - } else { - Log.w(TAG, "data '$what' requested by akono not found") - return null - } + return null } } @@ -144,6 +136,7 @@ class WalletBackendService : Service() { private val subscribers = LinkedList<Messenger>() override fun onCreate() { + Log.i(TAG, "onCreate in wallet backend service") akono = AkonoJni() akono.setLoadModuleHandler(AssetModuleLoader(application.assets)) akono.setGetDataHandler(AssetDataHandler(application.assets)) @@ -161,13 +154,12 @@ class WalletBackendService : Service() { super.onCreate() } - fun sendInitMessage() { + private fun sendInitMessage() { val msg = JSONObject() msg.put("operation", "init") val args = JSONObject() msg.put("args", args) args.put("persistentStoragePath", "${application.filesDir}/talerwalletdb.json") - akono.sendMessage(msg.toString()) } @@ -242,30 +234,34 @@ class WalletBackendService : Service() { return messenger.binder } + private fun sendNotify() { + var rm: LinkedList<Messenger>? = null + for (s in subscribers) { + val m = Message.obtain(null, MSG_NOTIFY) + try { + s.send(m) + } catch (e: RemoteException) { + if (rm == null) { + rm = LinkedList<Messenger>() + } + rm.add(s) + subscribers.remove(s) + } + } + if (rm != null) { + for (s in rm) { + subscribers.remove(s) + } + } + } + private fun handleAkonoMessage(messageStr: String) { Log.v(TAG, "got back message: ${messageStr}") val message = JSONObject(messageStr) val type = message.getString("type") when (type) { "notification" -> { - var rm: LinkedList<Messenger>? = null - for (s in subscribers) { - val m = Message.obtain(null, MSG_NOTIFY) - try { - s.send(m) - } catch (e: RemoteException) { - if (rm == null) { - rm = LinkedList<Messenger>() - } - rm.add(s) - subscribers.remove(s) - } - } - if (rm != null) { - for (s in rm) { - subscribers.remove(s) - } - } + sendNotify() } "tunnelHttp" -> { Log.v(TAG, "got http tunnel request!") @@ -280,6 +276,10 @@ class WalletBackendService : Service() { when (operation) { "init" -> { Log.v(TAG, "got response for init operation") + sendNotify() + } + "reset" -> { + exitProcess(1) } else -> { val id = message.getInt("id") @@ -298,6 +298,7 @@ class WalletBackendService : Service() { b.putString("response", "{}") } b.putInt("requestID", rd.clientRequestID) + b.putString("operation", operation) rd.messenger.send(m) } } diff --git a/app/src/main/java/net/taler/wallet/crypto/Encoding.kt b/app/src/main/java/net/taler/wallet/crypto/Encoding.kt new file mode 100644 index 0000000..f64dcae --- /dev/null +++ b/app/src/main/java/net/taler/wallet/crypto/Encoding.kt @@ -0,0 +1,116 @@ +package net.taler.wallet.crypto + +import java.io.ByteArrayOutputStream + +class EncodingException : Exception("Invalid encoding") + + +object Base32Crockford { + + private fun ByteArray.getIntAt(index: Int): Int { + val x = this[index].toInt() + return if (x >= 0) x else (x + 256) + } + + private var encTable = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + + fun encode(data: ByteArray): String { + val sb = StringBuilder() + val size = data.size + var bitBuf = 0 + var numBits = 0 + var pos = 0 + while (pos < size || numBits > 0) { + if (pos < size && numBits < 5) { + val d = data.getIntAt(pos++) + bitBuf = (bitBuf shl 8) or d + numBits += 8 + } + if (numBits < 5) { + // zero-padding + bitBuf = bitBuf shl (5 - numBits) + numBits = 5 + } + val v = bitBuf.ushr(numBits - 5) and 31 + sb.append(encTable[v]) + numBits -= 5 + } + return sb.toString(); + } + + fun decode(encoded: String, out: ByteArrayOutputStream) { + val size = encoded.length + var bitpos = 0 + var bitbuf = 0 + var readPosition = 0 + + while (readPosition < size || bitpos > 0) { + //println("at position $readPosition with bitpos $bitpos") + if (readPosition < size) { + val v = getValue(encoded[readPosition++]) + bitbuf = (bitbuf shl 5) or v + bitpos += 5 + } + while (bitpos >= 8) { + val d = (bitbuf ushr (bitpos - 8)) and 0xFF + out.write(d) + bitpos -= 8 + } + if (readPosition == size && bitpos > 0) { + bitbuf = (bitbuf shl (8 - bitpos)) and 0xFF + bitpos = if (bitbuf == 0) 0 else 8 + } + } + } + + fun decode(encoded: String): ByteArray { + val out = ByteArrayOutputStream() + decode(encoded, out) + return out.toByteArray() + } + + private fun getValue(chr: Char): Int { + var a = chr + when (a) { + 'O', 'o' -> a = '0' + 'i', 'I', 'l', 'L' -> a = '1' + 'u', 'U' -> a = 'V' + } + if (a in '0'..'9') + return a - '0' + if (a in 'a'..'z') + a = Character.toUpperCase(a) + var dec = 0 + if (a in 'A'..'Z') { + if ('I' < a) dec++ + if ('L' < a) dec++ + if ('O' < a) dec++ + if ('U' < a) dec++ + return a - 'A' + 10 - dec + } + throw EncodingException() + } + + /** + * Compute the length of the resulting string when encoding data of the given size + * in bytes. + * + * @param dataSize size of the data to encode in bytes + * @return size of the string that would result from encoding + */ + fun calculateEncodedStringLength(dataSize: Int): Int { + return (dataSize * 8 + 4) / 5 + } + + /** + * Compute the length of the resulting data in bytes when decoding a (valid) string of the + * given size. + * + * @param stringSize size of the string to decode + * @return size of the resulting data in bytes + */ + fun calculateDecodedDataLength(stringSize: Int): Int { + return stringSize * 5 / 8 + } +} + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 5d9ac0a..a3aa93f 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -36,7 +36,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:text="0.6.0pre3 (Sat 02 Nov 2019)" /> + android:text="0.6.0pre4 (Tue 03 Dec 2019)" /> </LinearLayout> @@ -59,7 +59,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:text="0.6.0pre3 (Sat 02 Nov 2019, 70a2322940)" /> + android:text="0.6.0pre4 (Tue 3 Dec 2019, 829acdd3d9)" /> </LinearLayout> diff --git a/app/src/main/res/layout/fragment_show_balance.xml b/app/src/main/res/layout/fragment_show_balance.xml index b93d2c2..57f9309 100644 --- a/app/src/main/res/layout/fragment_show_balance.xml +++ b/app/src/main/res/layout/fragment_show_balance.xml @@ -42,5 +42,18 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button_pay_qr"/> + + <TextView + android:id="@+id/pending_operations_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Pending Operations:" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_pending" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbars="vertical"/> + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/app/src/main/res/layout/fragment_show_history.xml b/app/src/main/res/layout/fragment_show_history.xml index d730425..4ed11ac 100644 --- a/app/src/main/res/layout/fragment_show_history.xml +++ b/app/src/main/res/layout/fragment_show_history.xml @@ -1,13 +1,29 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".WalletHistory"> +<androidx.core.widget.NestedScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="15dp"> - <TextView + <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:text="The history of wallet operations will eventually be shown here. Currently this feature isn't implemented, sorry!"/> + android:orientation="vertical"> -</FrameLayout>
\ No newline at end of file + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_history" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbars="vertical"/> + + <TextView + android:id="@+id/list_history_placeholder" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="The wallet history is empty" + tools:visibility="gone"/> + </LinearLayout> + +</androidx.core.widget.NestedScrollView>
\ 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 new file mode 100644 index 0000000..864baa1 --- /dev/null +++ b/app/src/main/res/layout/history_row.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/history_text" + android:textSize="24sp" tools:text="My History Event"> + </TextView> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/pending_row.xml b/app/src/main/res/layout/pending_row.xml new file mode 100644 index 0000000..088ec99 --- /dev/null +++ b/app/src/main/res/layout/pending_row.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/pending_text" + android:textSize="24sp" tools:text="My Pending Operation"> + </TextView> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/menu/balance.xml b/app/src/main/res/menu/balance.xml new file mode 100644 index 0000000..f1565b1 --- /dev/null +++ b/app/src/main/res/menu/balance.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item android:id="@+id/reload_balance" + android:title="Reload Balance" + android:orderInCategory="100" + app:showAsAction="never"/> + <item android:id="@+id/retry_pending" + android:title="Retry Pending Operations" + android:orderInCategory="100" + app:showAsAction="never"/> +</menu> diff --git a/app/src/main/res/menu/history.xml b/app/src/main/res/menu/history.xml new file mode 100644 index 0000000..c8fb3e7 --- /dev/null +++ b/app/src/main/res/menu/history.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item android:id="@+id/reload_history" + android:title="Reload History" + android:orderInCategory="100" + app:showAsAction="never"/> +</menu> diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index e85427a..fac49f3 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -8,7 +8,7 @@ <fragment android:id="@+id/showBalance" android:name="net.taler.wallet.ShowBalance" - android:label="Balances" + android:label="Home" tools:layout="@layout/fragment_show_balance"> <action android:id="@+id/action_showBalance_to_promptPayment" diff --git a/app/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt b/app/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt new file mode 100644 index 0000000..b5d2772 --- /dev/null +++ b/app/src/test/java/net/taler/wallet/crypto/Base32CrockfordTest.kt @@ -0,0 +1,20 @@ +package net.taler.wallet.crypto + +import org.junit.Assert.* +import org.junit.Test + +class Base32CrockfordTest { + @Test + fun testBasic() { + val inputStr = "Hello, World" + val data = inputStr.toByteArray(Charsets.UTF_8) + val enc = Base32Crockford.encode(data) + println(enc) + val dec = Base32Crockford.decode(enc) + val recoveredInputStr = dec.toString(Charsets.UTF_8) + println(recoveredInputStr) + + val foo = Base32Crockford.decode("51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H2365338E9G6RT4AH1N6H13EGHR70RK6H1S6X2M4CSP8CSK8E1G88VKJH25610KGCHR8RWM4DJ47123CH9K89334D1S8N24ACJ48CR3EH256MR3AH1R711KCE9N6S134GSN6RW46D1H6CV3CDHJ6D0KEDHR6D24CD248MWKADHJ6WT34D25712KCD2474V46EA18H2M4GHM6WTK2E216S14CD238GSK0G9G692KCDHM6RW34CT16MV3CG9P60S34C1G70SMCHHQ8CVKJG9K6CVK6GHK70R46HJ26CR4AE9M8523ADHS8RR3EE1R74S32CHP6N1K0GT38D1M6C1R84TM2E9N8MSK2C1J71248E9H6H1MCD9J70VK4GSG6124CCHR6RS4ADSH8N0M4H1G84R4CD1G8D24AG9N6RR48DT1712K6GJ26X232DT36N0K4C9M8H236HJ48N2K4G9H8GVM8E1P8GSM6E9K891K4CSN65348C26611M8DHJ8S1M6H9G8H338CHS6GV3CD9K64S3GCHR8H2M6GJ58MT3EHA26S232GSJ6GTMAGA570W44DA2852KEDSR8MTKEGA460T3CCT18MR48CHK6WWKEGJ460WK4EA568VM6GSJ70T32CA461234DJ66RS34DHM6D242CT46MV3JDA584S4ADSM6S1MAE1P6GTKEGA68N1M8E216WRMAGHM6RR4ADSJ8MR3EDJ2690KAD9H6H346D9R88RKECSN8RRKJC1N74W34DSQ60W48DSJ8S1K0DSH8D1M4E1J6H1M2D1S8S33CG9R6RSMCH9K4CMGM81051JJ08SG64R30C1H4CMGM81054520A8A00") + println(foo.toString(Charsets.UTF_8)) + } +} |