diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index de61cce..62c9b15 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -11,7 +12,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme"
+ tools:ignore="GoogleAppIndexingWarning">
@@ -19,7 +21,8 @@
-
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java b/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java
index f2a9638..534bd59 100644
--- a/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java
+++ b/sample/src/main/java/cy/agorise/labs/sample/CallsActivity.java
@@ -17,6 +17,8 @@ import cy.agorise.graphenej.RPC;
public class CallsActivity extends AppCompatActivity {
+ private static final String REMOVE_CURRENT_NODE = "remove_current_node";
+
@BindView(R.id.call_list)
RecyclerView mRecyclerView;
@@ -50,7 +52,8 @@ public class CallsActivity extends AppCompatActivity {
RPC.CALL_SET_SUBSCRIBE_CALLBACK,
RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES,
RPC.CALL_GET_KEY_REFERENCES,
- RPC.CALL_GET_ACCOUNT_BALANCES
+ RPC.CALL_GET_ACCOUNT_BALANCES,
+ REMOVE_CURRENT_NODE
};
@NonNull
@@ -71,6 +74,8 @@ public class CallsActivity extends AppCompatActivity {
Intent intent;
if(selectedCall.equals(RPC.CALL_SET_SUBSCRIBE_CALLBACK)){
intent = new Intent(CallsActivity.this, SubscriptionActivity.class);
+ } else if (selectedCall.equals(REMOVE_CURRENT_NODE)){
+ intent = new Intent(CallsActivity.this, RemoveNodeActivity.class);
}else{
intent = new Intent(CallsActivity.this, PerformCallActivity.class);
intent.putExtra(Constants.KEY_SELECTED_CALL, selectedCall);
diff --git a/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java b/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java
new file mode 100644
index 0000000..81f7a6b
--- /dev/null
+++ b/sample/src/main/java/cy/agorise/labs/sample/RemoveNodeActivity.java
@@ -0,0 +1,274 @@
+package cy.agorise.labs.sample;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.util.SortedList;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import cy.agorise.graphenej.api.android.NetworkService;
+import cy.agorise.graphenej.network.FullNode;
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.subjects.PublishSubject;
+
+public class RemoveNodeActivity extends AppCompatActivity implements ServiceConnection {
+
+ private final String TAG = this.getClass().getName();
+
+ @BindView(R.id.rvNodes)
+ RecyclerView rvNodes;
+
+ FullNodesAdapter nodesAdapter;
+
+ // Comparator used to sort the nodes in ascending order
+ private final Comparator LATENCY_COMPARATOR = (a, b) ->
+ Double.compare(a.getLatencyValue(), b.getLatencyValue());
+
+ /* Network service connection */
+ private NetworkService mNetworkService;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_remove_node);
+
+ ButterKnife.bind(this);
+
+ rvNodes.setLayoutManager(new LinearLayoutManager(this));
+ nodesAdapter = new FullNodesAdapter(this, LATENCY_COMPARATOR);
+ rvNodes.setAdapter(nodesAdapter);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ NetworkService.LocalBinder binder = (NetworkService.LocalBinder) iBinder;
+ mNetworkService = binder.getService();
+
+ if(mNetworkService != null){
+ // PublishSubject used to announce full node latencies updates
+ PublishSubject fullNodePublishSubject = mNetworkService.getNodeLatencyObservable();
+ if(fullNodePublishSubject != null)
+ fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver);
+
+ List fullNodes = mNetworkService.getNodes();
+ nodesAdapter.add(fullNodes);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+
+ }
+
+ /**
+ * Observer used to be notified about node latency measurement updates.
+ */
+ private Observer nodeLatencyObserver = new Observer() {
+ @Override
+ public void onSubscribe(Disposable d) { }
+
+ @Override
+ public void onNext(FullNode fullNode) {
+ nodesAdapter.add(fullNode);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ Log.e(TAG,"nodeLatencyObserver.onError.Msg: "+e.getMessage());
+ }
+
+ @Override
+ public void onComplete() { }
+ };
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ // Bind to LocalService
+ Intent intent = new Intent(this, NetworkService.class);
+ bindService(intent, this, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unbindService(this);
+ }
+
+ class FullNodesAdapter extends RecyclerView.Adapter {
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView ivNodeStatus;
+ TextView tvNodeName;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+
+ ivNodeStatus = itemView.findViewById(R.id.ivNodeStatus);
+ tvNodeName = itemView.findViewById(R.id.tvNodeName);
+ }
+ }
+
+ private final SortedList mSortedList = new SortedList<>(FullNode.class, new SortedList.Callback() {
+ @Override
+ public void onInserted(int position, int count) {
+ notifyItemRangeInserted(position, count);
+ }
+
+ @Override
+ public void onRemoved(int position, int count) {
+ notifyItemRangeRemoved(position, count);
+ }
+
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ notifyItemMoved(fromPosition, toPosition);
+ }
+
+ @Override
+ public void onChanged(int position, int count) {
+ notifyItemRangeChanged(position, count);
+ }
+
+ @Override
+ public int compare(FullNode a, FullNode b) {
+ return mComparator.compare(a, b);
+ }
+
+ @Override
+ public boolean areContentsTheSame(FullNode oldItem, FullNode newItem) {
+ return oldItem.getLatencyValue() == newItem.getLatencyValue();
+ }
+
+ @Override
+ public boolean areItemsTheSame(FullNode item1, FullNode item2) {
+ return item1.getUrl().equals(item2.getUrl());
+ }
+ });
+
+ private final Comparator mComparator;
+
+ private Context mContext;
+
+ FullNodesAdapter(Context context, Comparator comparator) {
+ mContext = context;
+ mComparator = comparator;
+ }
+
+ private Context getContext() {
+ return mContext;
+ }
+
+ @NonNull
+ @Override
+ public FullNodesAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ Context context = parent.getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
+
+ View transactionView = inflater.inflate(R.layout.item_node, parent, false);
+
+ return new ViewHolder(transactionView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
+ final FullNode fullNode = mSortedList.get(position);
+
+ // Show the green check mark before the node name if that node is the one being used
+ if (fullNode.isConnected())
+ viewHolder.ivNodeStatus.setImageResource(R.drawable.ic_connected);
+ else
+ viewHolder.ivNodeStatus.setImageDrawable(null);
+
+ double latency = fullNode.getLatencyValue();
+
+ // Select correct color span according to the latency value
+ ForegroundColorSpan colorSpan;
+
+ if (latency < 400)
+ colorSpan = new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.colorPrimary));
+ else if (latency < 800)
+ colorSpan = new ForegroundColorSpan(Color.rgb(255,136,0)); // Holo orange
+ else
+ colorSpan = new ForegroundColorSpan(Color.rgb(204,0,0)); // Holo red
+
+ // Create a string with the latency number colored according to their amount
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ssb.append(fullNode.getUrl().replace("wss://", ""), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ ssb.append(" (");
+
+ // 2000 ms is the timeout of the websocket used to calculate the latency, therefore if the
+ // received latency is greater than such value we can assume the node was not reachable.
+ String ms = latency < 2000 ? String.format(Locale.US, "%.0f ms", latency) : "??";
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ssb.append(ms, colorSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ ssb.append(")");
+
+ viewHolder.tvNodeName.setText(ssb);
+ }
+
+ /**
+ * Functions that adds/updates a FullNode to the SortedList
+ */
+ public void add(FullNode fullNode) {
+ // Remove the old instance of the FullNode before adding a new one. My understanding is that
+ // the sorted list should be able to automatically find repeated elements and update them
+ // instead of adding duplicates but it wasn't working so I opted for manually removing old
+ // instances of FullNodes before adding the updated ones.
+ int removed = 0;
+ for (int i=0; i fullNodes) {
+ mSortedList.addAll(fullNodes);
+ }
+
+
+ @Override
+ public int getItemCount() {
+ return mSortedList.size();
+ }
+ }
+}
diff --git a/sample/src/main/res/drawable/ic_connected.xml b/sample/src/main/res/drawable/ic_connected.xml
new file mode 100644
index 0000000..5b57096
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_connected.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/layout/activity_remove_node.xml b/sample/src/main/res/layout/activity_remove_node.xml
new file mode 100644
index 0000000..a1cb381
--- /dev/null
+++ b/sample/src/main/res/layout/activity_remove_node.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/item_node.xml b/sample/src/main/res/layout/item_node.xml
new file mode 100644
index 0000000..1af107b
--- /dev/null
+++ b/sample/src/main/res/layout/item_node.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file