diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/CoinCommunity.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/CoinCommunity.kt index f0e709f77..8ccd6463d 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/CoinCommunity.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/CoinCommunity.kt @@ -1,10 +1,14 @@ package nl.tudelft.ipv8.android.demo -import android.util.JsonWriter +import android.util.Log import nl.tudelft.ipv8.Address import nl.tudelft.ipv8.Community import nl.tudelft.ipv8.IPv8 import nl.tudelft.ipv8.android.IPv8Android +import nl.tudelft.ipv8.android.demo.coin.CoinUtil import nl.tudelft.ipv8.attestation.trustchain.* +import nl.tudelft.ipv8.peerdiscovery.DiscoveryCommunity +import nl.tudelft.ipv8.util.hexToBytes +import nl.tudelft.ipv8.util.toHex import org.json.JSONObject import java.util.* import kotlin.collections.ArrayList @@ -32,9 +36,18 @@ class CoinCommunity: Community() { return IPv8Android.getInstance() } - public fun sendCurrency(amount: Double, toPublicKey: ByteArray = myPeer.publicKey.keyToBin()) { - val message = "Transaction amount: $amount bitcoins" - trustchain.createProposalBlock(message, toPublicKey, SW_JOIN_BLOCK) + private fun createTransactionData(entranceFee: Double, sharedWalletPublicKey: String, + votingThreshold: Int, trustchainPks: List, + bitcoinPks: List, uniqueId: String? = null): String { + val transactionValues = mapOf( + SW_UNIQUE_ID to (uniqueId ?: CoinUtil.randomUUID()), + SW_ENTRANCE_FEE to entranceFee, + SW_PK to sharedWalletPublicKey, + SW_VOTING_THRESHOLD to votingThreshold, + SW_TRUSTCHAIN_PKS to trustchainPks, + SW_BITCOIN_PKS to bitcoinPks + ) + return JSONObject(transactionValues).toString() } /** @@ -45,7 +58,7 @@ class CoinCommunity: Community() { */ public fun createSharedWallet(entranceFee: Double, votingThreshold: Int, bitcoinPk: ByteArray) { if (votingThreshold <= 0 || votingThreshold > 100) { - throw IllegalStateException("The voting threshold (%) for a shared wallet should be [0,100>") + throw IllegalStateException("The voting threshold (%) for a shared wallet should be <0,100]") } val trustchainPk = myPeer.publicKey.keyToBin() @@ -54,50 +67,43 @@ class CoinCommunity: Community() { // TODO: Create bitcoin wallet // TODO: Fill wallet with entrance fee - val transactionValues = mapOf( - SW_ENTRANCE_FEE to entranceFee, - SW_PK to sharedWalletPK, - SW_VOTING_THRESHOLD to votingThreshold, - SW_TRUSTCHAIN_PKS to arrayListOf(trustchainPk), - SW_BITCOIN_PKS to arrayListOf(bitcoinPk) + val values = createTransactionData( + entranceFee, + sharedWalletPK.toHex(), + votingThreshold, + arrayListOf(trustchainPk.toHex()), + arrayListOf(bitcoinPk.toHex()) ) - val transaction = mapOf("message" to JSONObject(transactionValues).toString()) - // Create a self signed proposal block - val proposalBlock = trustchain.createProposalBlock(transaction, trustchainPk, SW_JOIN_BLOCK) - trustchain.createAgreementBlock(proposalBlock, mapOf("message" to "ack")) + trustchain.createProposalBlock(values, trustchainPk, SHARED_WALLET_BLOCK) } public fun joinSharedWallet(swBlockHash: ByteArray, bitcoinPk: ByteArray) { val swJoinBlock: TrustChainBlock = getTrustChainCommunity().database.getBlockWithHash(swBlockHash) ?: throw IllegalStateException("Shared Wallet not found given the hash: $swBlockHash") - if (!swJoinBlock.isAgreement) { - throw IllegalStateException("Shared Wallet block is not signed!") - } - - val entranceFee = swJoinBlock.transaction[SW_ENTRANCE_FEE] - val oldTrustchainPks = swJoinBlock.transaction[SW_BITCOIN_PKS] as ArrayList - val oldBitcoinPks = swJoinBlock.transaction[SW_JOIN_BLOCK] as ArrayList - val trustchainPk = myPeer.publicKey.keyToBin() + val parsedTransaction = CoinUtil.parseTransaction(swJoinBlock.transaction) + val oldTrustchainPks = CoinUtil.parseJSONArray(parsedTransaction.getJSONArray(SW_TRUSTCHAIN_PKS)) // TODO: Pay the entrance fee with bitcoinPk // TODO: Create new shared wallet using bitcoinPks - val newTrustchainPks: ArrayList = swJoinBlock.transaction[SW_TRUSTCHAIN_PKS] as ArrayList - newTrustchainPks.add(trustchainPk) + val newTrustchainPks: ArrayList = arrayListOf() + newTrustchainPks.addAll(oldTrustchainPks) + newTrustchainPks.add(myPeer.publicKey.keyToBin().toHex()) - val newBitcoinPks: ArrayList = swJoinBlock.transaction[SW_BITCOIN_PKS] as ArrayList - newBitcoinPks.add(bitcoinPk) + val newBitcoinPks: ArrayList = CoinUtil.parseJSONArray(parsedTransaction.getJSONArray(SW_BITCOIN_PKS)) + newBitcoinPks.add(bitcoinPk.toHex()) - val transaction = mapOf( - SW_ENTRANCE_FEE to swJoinBlock.transaction[SW_ENTRANCE_FEE], - SW_PK to swJoinBlock.transaction[SW_PK], - SW_VOTING_THRESHOLD to swJoinBlock.transaction[SW_VOTING_THRESHOLD], - SW_TRUSTCHAIN_PKS to newTrustchainPks, - SW_BITCOIN_PKS to newBitcoinPks + val values = createTransactionData( + parsedTransaction.getDouble(SW_ENTRANCE_FEE), + parsedTransaction.getString(SW_PK), + parsedTransaction.getInt(SW_VOTING_THRESHOLD), + newTrustchainPks, + newBitcoinPks, + parsedTransaction.getString(SW_UNIQUE_ID) ) for (swParticipantPk in oldTrustchainPks) { - trustchain.createProposalBlock(transaction, swParticipantPk, SW_JOIN_AGREEMENT_BLOCK) + trustchain.createProposalBlock(values, swParticipantPk.hexToBytes(), SHARED_WALLET_BLOCK) } // TODO: TIMEOUT, wait for votes, collect key parts @@ -106,14 +112,53 @@ class CoinCommunity: Community() { public fun transferFunds(oldSwPk: ByteArray, newSwPk: ByteArray) { // TODO: send funds to new wallet } + + private fun fetchSharedWalletBlocks(): List { + return getTrustChainCommunity().database.getBlocksWithType(SHARED_WALLET_BLOCK) + } + + /** + * Fetch the latest shared wallet block, based on a given block 'block'. + * The unique shared wallet id is used to find the most recent block in + * the 'sharedWalletBlocks' list. + */ + private fun fetchLatestSharedWalletBlock(block: TrustChainBlock, sharedWalletBlocks: List) + : TrustChainBlock? { + val walletId = CoinUtil.parseTransaction(block.transaction).getString(SW_UNIQUE_ID) + return sharedWalletBlocks + .filter{ CoinUtil.parseTransaction(it.transaction).getString(SW_UNIQUE_ID) == walletId } + .maxBy { it.timestamp.time } + } + + /** + * Discover shared wallets that you can join, return the latest (known) blocks + */ + public fun discoverSharedWallets(): List { + val sharedWalletBlocks = fetchSharedWalletBlocks() + // For every distinct unique shared wallet, find the latest block + return sharedWalletBlocks + .distinctBy { CoinUtil.parseTransaction(it.transaction).getString(SW_UNIQUE_ID) } + .map { fetchLatestSharedWalletBlock(it, sharedWalletBlocks) ?: it } + } + + /** + * Fetch the shared wallet blocks that you are part of, based on your trustchain PK. + */ + public fun fetchLatestJoinedSharedWalletBlocks(): List { + return discoverSharedWallets().filter { + val blockData = CoinUtil.parseTransaction(it.transaction) + val userTrustchainPks = CoinUtil.parseJSONArray(blockData.getJSONArray(SW_TRUSTCHAIN_PKS)) + userTrustchainPks.contains(myPeer.publicKey.keyToBin().toHex()) + } + } companion object { - private const val SW_JOIN_BLOCK = "SW_JOIN" - private const val SW_JOIN_AGREEMENT_BLOCK = "SW_JOIN" - private const val SW_ENTRANCE_FEE = "SW_ENTRANCE_FEE" - private const val SW_PK = "SW_PK" - private const val SW_VOTING_THRESHOLD = "SW_VOTING_THRESHOLD" - private const val SW_TRUSTCHAIN_PKS = "SW_TRUSTCHAIN_PKS" - private const val SW_BITCOIN_PKS = "SW_BLOCKCHAIN_PKS" + public const val SHARED_WALLET_BLOCK = "SHARED_WALLET_BLOCK" + public const val SW_UNIQUE_ID = "SW_UNIQUE_ID" + public const val SW_ENTRANCE_FEE = "SW_ENTRANCE_FEE" + public const val SW_PK = "SW_PK" + public const val SW_VOTING_THRESHOLD = "SW_VOTING_THRESHOLD" + public const val SW_TRUSTCHAIN_PKS = "SW_TRUSTCHAIN_PKS" + public const val SW_BITCOIN_PKS = "SW_BLOCKCHAIN_PKS" } } diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/DemoApplication.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/DemoApplication.kt index 4f2021de4..ea3235e37 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/DemoApplication.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/DemoApplication.kt @@ -85,6 +85,12 @@ class DemoApplication : Application() { Log.d("TrustChainDemo", "onBlockReceived: ${block.blockId} ${block.transaction}") } }) + + trustchain.addListener(CoinCommunity.SHARED_WALLET_BLOCK, object : BlockListener { + override fun onBlockReceived(block: TrustChainBlock) { + Log.d("Coin", "onBlockReceived: ${block.blockId} ${block.transaction}") + } + }) } private fun createDiscoveryCommunity(): OverlayConfiguration { diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/CoinUtil.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/CoinUtil.kt new file mode 100644 index 000000000..5e3f1a254 --- /dev/null +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/CoinUtil.kt @@ -0,0 +1,37 @@ +package nl.tudelft.ipv8.android.demo.coin + +import nl.tudelft.ipv8.attestation.trustchain.TrustChainTransaction +import org.json.JSONArray +import org.json.JSONObject +import java.nio.ByteBuffer +import java.util.* +import kotlin.collections.ArrayList + + +object CoinUtil { + /** + * Generate a random 128 bit string + * From: https://sakthipriyan.com/2017/04/02/creating-base64-uuid-in-java.html + */ + @JvmStatic + fun randomUUID(): String { + val uuid: UUID = UUID.randomUUID() + val src: ByteArray = ByteBuffer.wrap(ByteArray(16)) + .putLong(uuid.mostSignificantBits) + .putLong(uuid.leastSignificantBits) + .array() + return Base64.getUrlEncoder().encodeToString(src).substring(0, 22) + } + + @JvmStatic + fun parseTransaction(transaction: TrustChainTransaction): JSONObject { + return JSONObject(transaction["message"].toString()) + } + + @JvmStatic + fun parseJSONArray(jsonArray: JSONArray): ArrayList { + return Array(jsonArray.length()) { + jsonArray.getString(it) + }.toCollection(ArrayList()) + } +} diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WalletManager.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WalletManager.kt index 06b49c78a..87570462d 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WalletManager.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WalletManager.kt @@ -44,7 +44,7 @@ class WalletManager(walletManagerConfiguration: WalletManagerConfiguration, wall kit = object : WalletAppKit(params, walletDir, filePrefix) { override fun onSetupCompleted() { - // Make a fresh new key if no keys in stored wallet. + // Make a fresh new key if no keys in stored wallet. if (wallet().keyChainGroupSize < 1) wallet().importKey(ECKey()) wallet().allowSpendingUnconfirmedTransactions() Log.i("Coin", "Coin: WalletManager started successfully.") diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WallterManagerConfiguration.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WalletManagerConfiguration.kt similarity index 100% rename from demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WallterManagerConfiguration.kt rename to demo-android/src/main/java/nl/tudelft/ipv8/android/demo/coin/WalletManagerConfiguration.kt diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/BaseFragment.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/BaseFragment.kt index 3b441cbc7..587bb02df 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/BaseFragment.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/BaseFragment.kt @@ -9,6 +9,7 @@ import nl.tudelft.ipv8.android.demo.DemoCommunity import nl.tudelft.ipv8.android.demo.TrustChainHelper import nl.tudelft.ipv8.attestation.trustchain.TrustChainCommunity +import nl.tudelft.ipv8.peerdiscovery.DiscoveryCommunity abstract class BaseFragment(@LayoutRes contentLayoutId: Int = 0) : Fragment(contentLayoutId) { protected val trustchain: TrustChainHelper by lazy { diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinFragment.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinFragment.kt index 15f7e91c5..da35ab1fe 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinFragment.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinFragment.kt @@ -10,7 +10,6 @@ import kotlinx.android.synthetic.main.fragment_bitcoin.* import nl.tudelft.ipv8.android.demo.R import nl.tudelft.ipv8.android.demo.coin.WalletManagerAndroid import nl.tudelft.ipv8.android.demo.ui.BaseFragment -import nl.tudelft.ipv8.util.hexToBytes /** * A simple [Fragment] subclass. @@ -21,31 +20,19 @@ class BitcoinFragment( override val controller: BitcoinViewController ) : BitcoinView, BaseFragment(R.layout.fragment_bitcoin) { - private var publicKeyReceiver: String = "" - private var bitcoinPrivateKey: String = "" - private var transactionAmount: Double = 0.0 - override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - button3.setOnClickListener { - publicKeyReceiver = pk_receiver.text.toString() - transactionAmount = tx_amount.text.toString().toDouble() - outputTextView.text = "PK Receiver: $publicKeyReceiver, Amount: $transactionAmount" - - getCoinCommunity().sendCurrency(transactionAmount, publicKeyReceiver.hexToBytes()) - } + initClickListeners() + } + private fun initClickListeners() { refreshButton.setOnClickListener { refresh() } - refresh() - } - - private fun refresh() { - val walletManager = WalletManagerAndroid.getInstance() - - Log.i("Coin", "Coin: ${walletManager.toSeed()}") + show_wallet_button.setOnClickListener { + controller.showView("MySharedWalletsFragment") + } create_wallet_button.setOnClickListener { controller.showView("CreateSWFragment") @@ -54,6 +41,14 @@ class BitcoinFragment( search_wallet_button.setOnClickListener { controller.showView("JoinNetworkFragment") } + + + } + + private fun refresh() { + val walletManager = WalletManagerAndroid.getInstance() + + Log.i("Coin", "Coin: ${walletManager.toSeed()}") } override fun onCreateView( diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinViewController.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinViewController.kt index 4a6f64010..d23b33419 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinViewController.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/BitcoinViewController.kt @@ -1,5 +1,8 @@ package nl.tudelft.ipv8.android.demo.ui.bitcoin +import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock + interface BitcoinViewController { fun showView(bitcoinViewName: String) + fun showSharedWalletTransactionView(sharedWalletBlock: TrustChainBlock) } diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/CreateSWFragment.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/CreateSWFragment.kt index 52d98d10d..d426802ec 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/CreateSWFragment.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/CreateSWFragment.kt @@ -1,6 +1,7 @@ package nl.tudelft.ipv8.android.demo.ui.bitcoin import android.os.Bundle +import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View @@ -37,6 +38,7 @@ class CreateSWFragment( val threshold = voting_threshold_tf.text.toString().toInt() getCoinCommunity().createSharedWallet(entranceFee, threshold, ByteArray(5)) + controller.showView("BitcoinFragment") } else { alert_label.text = "Entrance fee should be a double, threshold an integer, both >0" } @@ -46,7 +48,7 @@ class CreateSWFragment( val entranceFee = entrance_fee_tf.text.toString().toDoubleOrNull() val votingThreshold = voting_threshold_tf.text.toString().toIntOrNull() return entranceFee != null && entranceFee > 0 - && votingThreshold != null && votingThreshold > 0 && votingThreshold < 100 + && votingThreshold != null && votingThreshold > 0 && votingThreshold <= 100 } override fun onCreateView( diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkFragment.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkFragment.kt index cbd238b75..1e7b99d09 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkFragment.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkFragment.kt @@ -1,12 +1,17 @@ package nl.tudelft.ipv8.android.demo.ui.bitcoin 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 kotlinx.android.synthetic.main.fragment_join_network.* +import nl.tudelft.ipv8.android.demo.CoinCommunity import nl.tudelft.ipv8.android.demo.R import nl.tudelft.ipv8.android.demo.ui.BaseFragment +import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock +import nl.tudelft.ipv8.util.toHex /** * A simple [Fragment] subclass. @@ -16,8 +21,23 @@ import nl.tudelft.ipv8.android.demo.ui.BaseFragment class JoinNetworkFragment ( override val controller: BitcoinViewController ) : BitcoinView, BaseFragment(R.layout.fragment_join_network) { + private val tempBitcoinPk = ByteArray(2) + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) + + val sharedWalletBlocks = getCoinCommunity().discoverSharedWallets() + val publicKey = getTrustChainCommunity().myPeer.publicKey.keyToBin().toHex() + val adaptor = SharedWalletListAdapter(this, sharedWalletBlocks, publicKey, "Click to join") + list_view.adapter = adaptor + list_view.setOnItemClickListener { _, view, position, id -> + joinSharedWalletClicked(sharedWalletBlocks[position]) + Log.i("Coin", "Clicked: $view, $position, $id") + } + } + + private fun joinSharedWalletClicked(block: TrustChainBlock) { + getCoinCommunity().joinSharedWallet(block.calculateHash(), tempBitcoinPk) } override fun onCreateView( @@ -29,12 +49,6 @@ class JoinNetworkFragment ( } companion object { - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @return A new instance of fragment bitcoinFragment. - */ @JvmStatic fun newInstance(controller: BitcoinViewController) = JoinNetworkFragment(controller) } diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkSteps.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkSteps.kt new file mode 100644 index 000000000..36ecdeab7 --- /dev/null +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/JoinNetworkSteps.kt @@ -0,0 +1,78 @@ +package nl.tudelft.ipv8.android.demo.ui.bitcoin + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import nl.tudelft.ipv8.android.demo.R + +private const val PUBLIC_KEY = "publicKey" +private const val VOTING_THRESHOLD = "votingThreshold" +private const val ENTRANCE_FEE = "entranceFee" +private const val USERS = "users" + + +/** + * A simple [Fragment] subclass. + * Use the [JoinNetworkSteps.newInstance] factory method to + * create an instance of this fragment. + */ +// TODO: Fix Awkward Parameter Usage. +class JoinNetworkSteps : Fragment() { + private var publicKey: String = "" + private var votingThreshold: String = "" + private var entranceFee: String = "" + private var users: String = "" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + publicKey = it.getString(PUBLIC_KEY, PUBLIC_KEY) + votingThreshold = it.getString(VOTING_THRESHOLD, VOTING_THRESHOLD) + entranceFee = it.getString(ENTRANCE_FEE, ENTRANCE_FEE) + users = it.getString(USERS, USERS) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_join_network_steps, container, false) + view.findViewById(R.id.public_key_proposal).text = publicKey + view.findViewById(R.id.voting_threshold_proposal).text = votingThreshold + view.findViewById(R.id.entrance_fee_proposal).text = entranceFee + view.findViewById(R.id.users_proposal).text = users + return view + } + + companion object { + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * + * @param param1 Parameter 1. + * @param param2 Parameter 2. + * @return A new instance of fragment joinNetworkSteps. + */ + // TODO: Rename and change types and number of parameters + @JvmStatic + fun newInstance( + publicKey: String, + votingThreshold: String, + entranceFee: String, + users: String + ) = + JoinNetworkSteps().apply { + arguments = Bundle().apply { + putString(PUBLIC_KEY, publicKey) + putString(VOTING_THRESHOLD, votingThreshold) + putString(ENTRANCE_FEE, entranceFee) + putString(USERS, users) + } + } + } +} diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/LandingBitcoinFragment.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/LandingBitcoinFragment.kt index 9d8e1d145..a4d55387f 100644 --- a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/LandingBitcoinFragment.kt +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/LandingBitcoinFragment.kt @@ -5,8 +5,12 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import nl.tudelft.ipv8.android.demo.CoinCommunity import nl.tudelft.ipv8.android.demo.R +import nl.tudelft.ipv8.android.demo.coin.CoinUtil import nl.tudelft.ipv8.android.demo.ui.BaseFragment +import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock +import nl.tudelft.ipv8.util.toHex import kotlin.IllegalArgumentException /** @@ -14,7 +18,8 @@ import kotlin.IllegalArgumentException * Use the [BitcoinFragment.newInstance] factory method to * create an instance of this fragment. */ -class LandingBitcoinFragment : BaseFragment(R.layout.fragment_landing_bitcoin), BitcoinViewController { +class LandingBitcoinFragment : BaseFragment(R.layout.fragment_landing_bitcoin), + BitcoinViewController { /** * Loads the view fragments and map them to strings as identifiers. @@ -22,7 +27,8 @@ class LandingBitcoinFragment : BaseFragment(R.layout.fragment_landing_bitcoin), private val bitcoinViews = mapOf( "BitcoinFragment" to BitcoinFragment.newInstance(this), "JoinNetworkFragment" to JoinNetworkFragment.newInstance(this), - "CreateSWFragment" to CreateSWFragment.newInstance(this) + "CreateSWFragment" to CreateSWFragment.newInstance(this), + "MySharedWalletsFragment" to MySharedWalletFragment.newInstance(this) ) override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -56,6 +62,19 @@ class LandingBitcoinFragment : BaseFragment(R.layout.fragment_landing_bitcoin), transaction.commit() } + override fun showSharedWalletTransactionView(sharedWalletBlock: TrustChainBlock) { + val publicKey = sharedWalletBlock.publicKey.toHex() + val parsedTransaction = CoinUtil.parseTransaction(sharedWalletBlock.transaction) + val votingThresholdText = "${parsedTransaction.getInt(CoinCommunity.SW_VOTING_THRESHOLD)} %" + val entranceFeeText = "${parsedTransaction.getDouble(CoinCommunity.SW_ENTRANCE_FEE)} BTC" + val users = "${parsedTransaction.getJSONArray(CoinCommunity.SW_TRUSTCHAIN_PKS).length()} user(s) in this shared wallet" + val fragment = JoinNetworkSteps.newInstance(publicKey, votingThresholdText, entranceFeeText, users) + val transaction = parentFragmentManager.beginTransaction() + transaction.replace(R.id.landing_bitcoin_container, fragment) + transaction.addToBackStack(null) + transaction.commit() + } + companion object { /** * Use this factory method to create a new instance of diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/MySharedWalletsFragment.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/MySharedWalletsFragment.kt new file mode 100644 index 000000000..53f78fe1a --- /dev/null +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/MySharedWalletsFragment.kt @@ -0,0 +1,52 @@ +package nl.tudelft.ipv8.android.demo.ui.bitcoin + +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 kotlinx.android.synthetic.main.fragment_join_network.* +import nl.tudelft.ipv8.android.demo.R +import nl.tudelft.ipv8.android.demo.ui.BaseFragment +import nl.tudelft.ipv8.util.toHex + + +/** + * A simple [Fragment] subclass. + * Use the [MySharedWalletFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class MySharedWalletFragment( + override val controller: BitcoinViewController +) : BitcoinView, BaseFragment(R.layout.fragment_my_shared_wallets) { + + private fun initListView() { + val sharedWalletBlocks = getCoinCommunity().fetchLatestJoinedSharedWalletBlocks() + val publicKey = getTrustChainCommunity().myPeer.publicKey.keyToBin().toHex() + val adaptor = SharedWalletListAdapter(this, sharedWalletBlocks, publicKey, "Click to enter wallet") + list_view.adapter = adaptor + list_view.setOnItemClickListener { _, view, position, id -> + controller.showSharedWalletTransactionView(sharedWalletBlocks[position]) + Log.i("Coin", "Clicked: $view, $position, $id") + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + initListView() + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_my_shared_wallets, container, false) + } + + companion object { + @JvmStatic + fun newInstance(controller: BitcoinViewController) = MySharedWalletFragment(controller) + } +} diff --git a/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/SharedWalletListAdapter.kt b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/SharedWalletListAdapter.kt new file mode 100644 index 000000000..c262aca3c --- /dev/null +++ b/demo-android/src/main/java/nl/tudelft/ipv8/android/demo/ui/bitcoin/SharedWalletListAdapter.kt @@ -0,0 +1,65 @@ +package nl.tudelft.ipv8.android.demo.ui.bitcoin + +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import nl.tudelft.ipv8.android.demo.CoinCommunity +import nl.tudelft.ipv8.android.demo.R +import nl.tudelft.ipv8.android.demo.coin.CoinUtil +import nl.tudelft.ipv8.android.demo.ui.BaseFragment +import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock +import nl.tudelft.ipv8.util.toHex + +class SharedWalletListAdapter( + private val context: BaseFragment, + private val items: List, + private val myPublicKey: String, + private val listButtonText: String) : BaseAdapter() { + + override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View { + val view = context.layoutInflater.inflate(R.layout.join_sw_row_data, null, false) + + val parsedTransaction = CoinUtil.parseTransaction(items[p0].transaction) + val walletId = view.findViewById(R.id.sw_id_item_t) + val votingThreshold = view.findViewById(R.id.sw_threshold_vt) + val entranceFee = view.findViewById(R.id.sw_entrance_fee_vt) + val nrOfUsers = view.findViewById(R.id.nr_of_users_tv) + val inWallet = view.findViewById(R.id.you_joined_tv) + val yourVotes = view.findViewById(R.id.your_votes_tv) + val clickToJoin = view.findViewById(R.id.click_to_join) + + val trustchainPks = CoinUtil.parseJSONArray(parsedTransaction.getJSONArray(CoinCommunity.SW_TRUSTCHAIN_PKS)) + + val walletIdText = "${parsedTransaction.getString(CoinCommunity.SW_UNIQUE_ID)}" + val votingThresholdText = "${parsedTransaction.getInt(CoinCommunity.SW_VOTING_THRESHOLD)} %" + val entranceFeeText = "${parsedTransaction.getDouble(CoinCommunity.SW_ENTRANCE_FEE)} BTC" + val users = "${trustchainPks.size} user(s) in this shared wallet" + val inWalletText = "${trustchainPks.contains(myPublicKey)}" + val votes = "${trustchainPks.filter { it == myPublicKey }.size}" + + walletId.text = walletIdText + votingThreshold.text = votingThresholdText + entranceFee.text = entranceFeeText + nrOfUsers.text = users + inWallet.text = inWalletText + yourVotes.text = votes + clickToJoin.text = listButtonText + + return view + } + + override fun getItem(p0: Int): Any { + return items[p0] + } + + override fun getItemId(p0: Int): Long { + return p0.toLong() + } + + override fun getCount(): Int { + return items.size + } +} + diff --git a/demo-android/src/main/res/layout/fragment_bitcoin.xml b/demo-android/src/main/res/layout/fragment_bitcoin.xml index 43da4dbca..678a6b3fd 100644 --- a/demo-android/src/main/res/layout/fragment_bitcoin.xml +++ b/demo-android/src/main/res/layout/fragment_bitcoin.xml @@ -1,11 +1,10 @@ - - + android:layout_height="match_parent" + android:orientation="vertical"> - - - - - - - - - - - - - - -