Improved Nodes Dialog.

- Made a couple of improvements to the Nodes Dialog in the Settings, so that it complies with the Nodes Dialog in PalmPay.
This commit is contained in:
Severiano Jaramillo 2019-08-29 11:46:22 -05:00
parent e2637ddfa4
commit a42736b738
2 changed files with 83 additions and 91 deletions

View file

@ -10,62 +10,54 @@ import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.SortedListAdapterCallback
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.graphenej.network.FullNode import cy.agorise.graphenej.network.FullNode
import java.util.*
/** /**
* Adapter used to populate the elements of the Bitshares nodes dialog in order to show a list of * Adapter used to populate the elements of the Bitshares nodes dialog in order to show a list of
* nodes with their latency. * nodes with their latency.
*/ */
class FullNodesAdapter(private val context: Context) : RecyclerView.Adapter<FullNodesAdapter.ViewHolder>() { class FullNodesAdapter(private val context: Context) :
val TAG: String = this.javaClass.name RecyclerView.Adapter<FullNodesAdapter.ViewHolder>() {
private val mComparator = companion object {
Comparator<FullNode> { a, b -> java.lang.Double.compare(a.latencyValue, b.latencyValue) } private const val TAG = "FullNodesAdapter"
private const val MAX_LATENCY = 9999
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvNodeName: TextView = itemView.findViewById(R.id.tvNodeName) val tvNodeName: TextView = itemView.findViewById(R.id.tvNodeName)
val ivNodeStatus: ImageView = itemView.findViewById(R.id.ivNodeStatus) val ivNodeStatus: ImageView = itemView.findViewById(R.id.ivNodeStatus)
} }
private val mSortedList = SortedList(FullNode::class.java, object : SortedList.Callback<FullNode>() { private val mSortedList = SortedList(FullNode::class.java,
override fun onInserted(position: Int, count: Int) { object : SortedListAdapterCallback<FullNode>(this) {
notifyItemRangeInserted(position, count)
}
override fun onRemoved(position: Int, count: Int) {
notifyItemRangeRemoved(position, count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
notifyItemMoved(fromPosition, toPosition)
}
override fun onChanged(position: Int, count: Int) {
notifyItemRangeChanged(position, count)
}
override fun compare(a: FullNode, b: FullNode): Int { override fun compare(a: FullNode, b: FullNode): Int {
return mComparator.compare(a, b) return a.latencyValue.compareTo(b.latencyValue)
} }
override fun areContentsTheSame(oldItem: FullNode, newItem: FullNode): Boolean { override fun areContentsTheSame(oldItem: FullNode, newItem: FullNode): Boolean {
return oldItem.latencyValue == newItem.latencyValue return oldItem.latencyValue == newItem.latencyValue &&
oldItem.isConnected == oldItem.isConnected &&
oldItem.isRemoved == oldItem.isRemoved
} }
override fun areItemsTheSame(item1: FullNode, item2: FullNode): Boolean { override fun areItemsTheSame(oldItem: FullNode, newItem: FullNode): Boolean {
return item1.url == item2.url return oldItem.url == newItem.url
} }
}) })
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FullNodesAdapter.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val transactionView = inflater.inflate(R.layout.item_node, parent, false) val transactionView = inflater.inflate(R.layout.item_node, parent, false)
@ -96,9 +88,8 @@ class FullNodesAdapter(private val context: Context) : RecyclerView.Adapter<Full
ssb.append(fullNode.url.replace("wss://", ""), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ssb.append(fullNode.url.replace("wss://", ""), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
ssb.append(" (") ssb.append(" (")
// 2000 ms is the timeout of the websocket used to calculate the latency, therefore if the // Make sure that the latency shown is no greater than MAX_LATENCY
// received latency is greater than such value we can assume the node was not reachable. val ms = if(latency <= MAX_LATENCY) "%.0fms".format(latency) else ">%dms".format(MAX_LATENCY)
val ms = if(latency < 2000) "%.0f ms".format(latency) else "??"
ssb.append(ms, colorSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ssb.append(ms, colorSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
ssb.append(")") ssb.append(")")
@ -109,28 +100,36 @@ class FullNodesAdapter(private val context: Context) : RecyclerView.Adapter<Full
/** /**
* Functions that adds/updates a FullNode to the SortedList * Functions that adds/updates a FullNode to the SortedList
*/ */
fun add(fullNode: FullNode) { @Synchronized fun add(fullNode: FullNode) {
// Remove the old instance of the FullNode before adding a new one. My understanding is that var index = -1
// the sorted list should be able to automatically find repeated elements and update them for (i in 0 until mSortedList.size()) {
// instead of adding duplicates but it wasn't working so I opted for manually removing old if (mSortedList[i].url == fullNode.url) {
// instances of FullNodes before adding the updated ones. index = i
var removed = 0 break
for (i in 0 until mSortedList.size()) }
if (mSortedList[i-removed].url == (fullNode.url)) }
mSortedList.removeItemAt(i-removed++)
mSortedList.add(fullNode) if (index == -1) {
Log.d(TAG , "Adding node: ${fullNode.url}")
mSortedList.add(fullNode) // Add it if it does not exist
} else {
Log.d(TAG , "Updating node: ${fullNode.url}")
mSortedList.updateItemAt(index, fullNode) // Update it if it does exist
}
} }
/** /**
* Function that adds a whole list of nodes to the SortedList. It should only be used at the * Function that adds a whole list of nodes to the SortedList. It should only be used at the
* moment of populating the SortedList for the first time. * moment of populating the SortedList for the first time.
*/ */
fun add(fullNodes: List<FullNode>) { fun add(fullNodes: List<FullNode>) = fullNodes.forEach { fullNode ->
mSortedList.addAll(fullNodes) // Make sure we are not adding duplicate nodes when the app returns from another activity
if (fullNode.latencyValue < Long.MAX_VALUE-1)
add(fullNode)
} }
fun remove(fullNode: FullNode) { fun remove(fullNode: FullNode) {
Log.d(TAG , "Removing node: ${fullNode.url}")
mSortedList.remove(fullNode) mSortedList.remove(fullNode)
} }

View file

@ -32,11 +32,8 @@ import cy.agorise.graphenej.api.calls.GetAccounts
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties
import cy.agorise.graphenej.models.DynamicGlobalProperties import cy.agorise.graphenej.models.DynamicGlobalProperties
import cy.agorise.graphenej.models.JsonRpcResponse import cy.agorise.graphenej.models.JsonRpcResponse
import cy.agorise.graphenej.network.FullNode
import cy.agorise.graphenej.operations.AccountUpgradeOperationBuilder import cy.agorise.graphenej.operations.AccountUpgradeOperationBuilder
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.android.synthetic.main.fragment_settings.*
import org.bitcoinj.core.DumpedPrivateKey import org.bitcoinj.core.DumpedPrivateKey
@ -129,30 +126,7 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
initNightModeSwitch() initNightModeSwitch()
tvNetworkStatus.setOnClickListener { v -> tvNetworkStatus.setOnClickListener { v -> showNodesDialog(v) }
if (mNetworkService != null) {
// PublishSubject used to announce full node latencies updates
val fullNodePublishSubject = mNetworkService!!.nodeLatencyObservable
fullNodePublishSubject?.observeOn(AndroidSchedulers.mainThread())?.subscribe(nodeLatencyObserver)
val fullNodes = mNetworkService!!.nodes
nodesAdapter = FullNodesAdapter(v.context)
nodesAdapter?.add(fullNodes)
mNodesDialog = MaterialDialog(v.context)
.title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME))
.message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
.customListAdapter(nodesAdapter as FullNodesAdapter)
.negativeButton(android.R.string.ok)
.onDismiss { mHandler.removeCallbacks(mRequestDynamicGlobalPropertiesTask) }
mNodesDialog?.show()
// Registering a recurrent task used to poll for dynamic global properties requests
mHandler.post(mRequestDynamicGlobalPropertiesTask)
}
}
// Obtain the current Security Lock Option selected and display it in the screen // Obtain the current Security Lock Option selected and display it in the screen
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context) val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
@ -172,26 +146,45 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
btnUpgradeToLTM.setOnClickListener { onUpgradeToLTMButtonSelected() } btnUpgradeToLTM.setOnClickListener { onUpgradeToLTMButtonSelected() }
} }
/** private fun showNodesDialog(v: View) {
* Observer used to be notified about node latency measurement updates. if (mNetworkService != null) {
*/ val fullNodes = mNetworkService!!.nodes
private val nodeLatencyObserver = object : Observer<FullNode> {
override fun onSubscribe(d: Disposable) {
mDisposables.add(d)
}
override fun onNext(fullNode: FullNode) { nodesAdapter = FullNodesAdapter(v.context)
nodesAdapter?.add(fullNodes)
// PublishSubject used to announce full node latencies updates
val fullNodePublishSubject = mNetworkService!!.nodeLatencyObservable ?: return
val nodesDisposable = fullNodePublishSubject
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ fullNode ->
if (!fullNode.isRemoved) if (!fullNode.isRemoved)
nodesAdapter?.add(fullNode) nodesAdapter?.add(fullNode)
else else
nodesAdapter?.remove(fullNode) nodesAdapter?.remove(fullNode)
}, {
Log.e(TAG, "nodeLatencyObserver.onError.Msg: " + it.message)
}
)
mNodesDialog = MaterialDialog(v.context)
.title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME))
.message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
.customListAdapter(nodesAdapter as FullNodesAdapter)
.negativeButton(android.R.string.ok)
.onDismiss {
mHandler.removeCallbacks(mRequestDynamicGlobalPropertiesTask)
nodesDisposable.dispose()
} }
override fun onError(e: Throwable) { mNodesDialog?.show()
Log.e(TAG, "nodeLatencyObserver.onError.Msg: " + e.message)
}
override fun onComplete() {} // Registering a recurrent task used to poll for dynamic global properties requests
mHandler.post(mRequestDynamicGlobalPropertiesTask)
}
} }
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {