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:
parent
e2637ddfa4
commit
a42736b738
2 changed files with 83 additions and 91 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<*>) {
|
||||||
|
|
Loading…
Reference in a new issue