1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-11-26 06:11:19 +00:00

Added more info to repo/node list items.

This commit is contained in:
Felix Ableitner 2014-06-09 19:49:54 +02:00
parent 8e8de14ef5
commit 2ceca738ea
13 changed files with 436 additions and 44 deletions

View file

@ -1,20 +1,29 @@
package com.nutomic.syncthingandroid;
import android.content.Context;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Generates item views for node items.
*/
public class NodeAdapter extends ArrayAdapter<RestApi.Node> {
public class NodeAdapter extends ArrayAdapter<RestApi.Node>
implements RestApi.OnReceiveConnectionsListener {
private Map<String, RestApi.Connection> mConnections =
new HashMap<String, RestApi.Connection>();
public NodeAdapter(Context context) {
super(context, R.layout.node_list_item);
@ -27,12 +36,39 @@ public class NodeAdapter extends ArrayAdapter<RestApi.Node> {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.node_list_item, parent, false);
}
TextView name = (TextView) convertView.findViewById(R.id.name);
TextView status = (TextView) convertView.findViewById(R.id.status);
TextView download = (TextView) convertView.findViewById(R.id.download);
TextView upload = (TextView) convertView.findViewById(R.id.upload);
name.setText(getItem(position).Name);
final String nodeId = getItem(position).NodeID;
RestApi.Connection conn = mConnections.get(nodeId);
Resources res = getContext().getResources();
if (conn != null) {
if (conn.Completion == 100) {
status.setText(res.getString(R.string.node_up_to_date));
status.setTextColor(res.getColor(R.color.text_green));
}
else {
status.setText(res.getString(R.string.node_syncing, conn.Completion));
status.setTextColor(res.getColor(R.color.text_blue));
}
download.setText(RestApi.readableTransferRate(getContext(), conn.InBits));
upload.setText(RestApi.readableTransferRate(getContext(), conn.OutBits));
}
else {
download.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
upload.setText("0 " + res.getStringArray(R.array.transfer_rate_units)[0]);
status.setText(res.getString(R.string.node_disconnected));
status.setTextColor(res.getColor(R.color.text_red));
}
return convertView;
}
/**
* Replacement for addAll, which is not implemented on lower API levels.
*/
@ -42,4 +78,21 @@ public class NodeAdapter extends ArrayAdapter<RestApi.Node> {
}
}
/**
* Requests new connection info for all nodes visible in listView.
*/
public void updateConnections(RestApi api, ListView listView) {
for (int i = 0; i < getCount(); i++) {
if ( i >= listView.getFirstVisiblePosition() &&
i <= listView.getLastVisiblePosition()) {
api.getConnections(this);
}
}
}
@Override
public void onReceiveConnections(Map<String, RestApi.Connection> connections) {
mConnections = connections;
notifyDataSetInvalidated();
}
}

View file

@ -5,16 +5,21 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import java.util.HashMap;
import java.util.List;
/**
* Generates item views for repository items.
*/
public class RepositoryAdapter extends ArrayAdapter<RestApi.Repository> {
public class RepositoryAdapter extends ArrayAdapter<RestApi.Repository>
implements RestApi.OnReceiveModelListener {
private HashMap<String, RestApi.Model> mModels = new HashMap<String, RestApi.Model>();
public RepositoryAdapter(Context context) {
super(context, R.layout.node_list_item);
@ -27,12 +32,37 @@ public class RepositoryAdapter extends ArrayAdapter<RestApi.Repository> {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.repository_list_item, parent, false);
}
TextView id = (TextView) convertView.findViewById(R.id.id);
TextView state = (TextView) convertView.findViewById(R.id.state);
TextView folder = (TextView) convertView.findViewById(R.id.folder);
TextView progress = (TextView) convertView.findViewById(R.id.progress);
TextView invalid = (TextView) convertView.findViewById(R.id.invalid);
id.setText(getItem(position).ID);
state.setTextColor(getContext().getResources().getColor(R.color.text_green));
folder.setText((getItem(position).Directory));
RestApi.Model model = mModels.get(getItem(position).ID);
if (model != null) {
state.setText(getContext().getString(R.string.repo_progress_format, model.state,
(model.globalBytes <= 0)
? 100
: (int) ((model.localBytes / (float) model.globalBytes) * 100)));
progress.setText(
RestApi.readableFileSize(getContext(), model.localBytes) + " / " +
RestApi.readableFileSize(getContext(), model.globalBytes)
);
invalid.setText(model.invalid);
invalid.setVisibility((model.invalid.equals("")) ? View.INVISIBLE : View.VISIBLE);
}
else {
invalid.setVisibility(View.INVISIBLE);
}
return convertView;
}
/**
* Replacement for addAll, which is not implemented on lower API levels.
*/
@ -42,4 +72,21 @@ public class RepositoryAdapter extends ArrayAdapter<RestApi.Repository> {
}
}
/**
* Requests updated model info from the api for all visible items.
*/
public void updateModel(RestApi api, ListView listView) {
for (int i = 0; i < getCount(); i++) {
if ( i >= listView.getFirstVisiblePosition() &&
i <= listView.getLastVisiblePosition()) {
api.getModel(getItem(i).ID, this);
}
}
}
@Override
public void onReceiveModel(String repoId, RestApi.Model model) {
mModels.put(repoId, model);
notifyDataSetChanged();
}
}

View file

@ -18,12 +18,12 @@ package com.nutomic.syncthingandroid.gui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.nutomic.syncthingandroid.R;
@ -117,8 +117,10 @@ public abstract class LoadingListFragment extends Fragment implements RestApi.On
/**
* Called when the list adapter should be set.
* @param activity
*/
public abstract void onInitAdapter(MainActivity activity);
public ListView getListView() {
return mListFragment.getListView();
}
}

View file

@ -6,7 +6,6 @@ import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@ -119,14 +118,14 @@ public class LocalNodeInfoFragment extends Fragment
public void onReceiveSystemInfo(RestApi.SystemInfo info) {
mNodeId.setText(info.myID);
mCpuUsage.setText(new DecimalFormat("0.00").format(info.cpuPercent) + "%");
mRamUsage.setText(mActivity.getApi().readableFileSize(info.sys));
mRamUsage.setText(RestApi.readableFileSize(mActivity, info.sys));
if (info.extAnnounceOK) {
mAnnounceServer.setText("Online");
mAnnounceServer.setTextColor(getResources().getColor(android.R.color.holo_green_light));
mAnnounceServer.setTextColor(getResources().getColor(R.color.text_green));
}
else {
mAnnounceServer.setText("Offline");
mAnnounceServer.setTextColor(getResources().getColor(android.R.color.holo_red_light));
mAnnounceServer.setTextColor(getResources().getColor(R.color.text_red));
}
}
@ -136,8 +135,8 @@ public class LocalNodeInfoFragment extends Fragment
@Override
public void onReceiveConnections(Map<String, RestApi.Connection> connections) {
RestApi.Connection c = connections.get(RestApi.LOCAL_NODE_CONNECTIONS);
mDownload.setText(mActivity.getApi().readableTransferRate(c.InBytesTotal));
mUpload.setText(mActivity.getApi().readableTransferRate(c.OutBytesTotal));
mDownload.setText(RestApi.readableTransferRate(mActivity, c.InBits));
mUpload.setText(RestApi.readableTransferRate(mActivity, c.OutBits));
}
/**

View file

@ -3,6 +3,10 @@ package com.nutomic.syncthingandroid.gui;
import com.nutomic.syncthingandroid.NodeAdapter;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
import java.util.Timer;
import java.util.TimerTask;
/**
* Displays a list of all existing nodes.
@ -10,11 +14,48 @@ import com.nutomic.syncthingandroid.syncthing.RestApi;
public class NodesFragment extends LoadingListFragment implements
RestApi.OnApiAvailableListener {
private NodeAdapter mAdapter;
private Timer mTimer;
private boolean mInitialized = false;
@Override
public void onInitAdapter(MainActivity activity) {
NodeAdapter adapter = new NodeAdapter(activity);
adapter.add(activity.getApi().getNodes());
setListAdapter(adapter, R.string.nodes_list_empty);
mAdapter = new NodeAdapter(activity);
mAdapter.add(activity.getApi().getNodes());
setListAdapter(mAdapter, R.string.nodes_list_empty);
mInitialized = true;
}
private void updateList() {
if (!mInitialized)
return;
MainActivity activity = (MainActivity) getActivity();
if (activity != null) {
mAdapter.updateConnections(activity.getApi(), getListView());
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
updateList();
}
}, 0, SyncthingService.GUI_UPDATE_INTERVAL);
}
else if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}
}

View file

@ -3,6 +3,10 @@ package com.nutomic.syncthingandroid.gui;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.RepositoryAdapter;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
import java.util.Timer;
import java.util.TimerTask;
/**
* Displays a list of all existing repositories.
@ -10,11 +14,48 @@ import com.nutomic.syncthingandroid.syncthing.RestApi;
public class RepositoriesFragment extends LoadingListFragment implements
RestApi.OnApiAvailableListener {
private RepositoryAdapter mAdapter;
private Timer mTimer;
private boolean mInitialized = false;
@Override
public void onInitAdapter(MainActivity activity) {
RepositoryAdapter adapter = new RepositoryAdapter(activity);
adapter.add(activity.getApi().getRepositories());
setListAdapter(adapter, R.string.repositories_list_empty);
mAdapter = new RepositoryAdapter(activity);
mAdapter.add(activity.getApi().getRepositories());
setListAdapter(mAdapter, R.string.repositories_list_empty);
mInitialized = true;
}
private void updateList() {
if (!mInitialized)
return;
MainActivity activity = (MainActivity) getActivity();
if (activity != null) {
mAdapter.updateModel(activity.getApi(), getListView());
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
updateList();
}
}, 0, SyncthingService.GUI_UPDATE_INTERVAL);
}
else if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}
}

View file

@ -5,15 +5,19 @@ import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
/**
* Performs a GET request with no parameters to the URL in uri[0] with the path in uri[1] and
@ -31,16 +35,24 @@ public class GetTask extends AsyncTask<String, Void, String> {
public static final String URI_CONNECTIONS = "/rest/connections";
public static final String URI_MODEL = "/rest/model";
/**
* params[0] Syncthing hostname
* params[1] URI to call
* params[2] Syncthing API key
* params[3] optional parameter key
* params[4] optional parameter value
*/
@Override
protected String doInBackground(String... params) {
String fullUri = params[0] + params[1];
Log.i(TAG, "Sending GET request to " + fullUri);
HttpClient httpclient = new DefaultHttpClient();
if (params.length == 5) {
LinkedList<NameValuePair> urlParams = new LinkedList<NameValuePair>();
urlParams.add(new BasicNameValuePair(params[2], params[3]));
fullUri += "?" + URLEncodedUtils.format(urlParams, "utf-8");
}
HttpGet get = new HttpGet(fullUri);
get.addHeader(new BasicHeader("X-API-Key", params[2]));

View file

@ -94,9 +94,26 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
public String At;
public long InBytesTotal;
public long OutBytesTotal;
public long InBits;
public long OutBits;
public String Address;
public String ClientVersion;
public double Completion;
public int Completion;
}
public static class Model {
public long globalBytes;
public long globalDeleted;
public long globalFiles;
public long localBytes;
public long localDeleted;
public long localFiles;
public long inSyncBytes;
public long inSyncFiles;
public long needBytes;
public long needFiles;
public String state;
public String invalid;
}
public interface OnApiAvailableListener {
@ -122,6 +139,17 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
private final NotificationManager mNotificationManager;
/**
* Stores the result of the last successful request to {@link GetTask#URI_CONNECTIONS},
* or an empty HashMap.
*/
private HashMap<String, Connection> mPreviousConnections = new HashMap<String, Connection>();
/**
* Stores the timestamp of the last successful request to {@link GetTask#URI_CONNECTIONS}.
*/
private long mPreviousConnectionTime = 0;
public RestApi(Context context, String url, String apiKey) {
mContext = context;
mUrl = url;
@ -420,8 +448,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
/**
* Converts a number of bytes to a human readable file size (eg 3.5 GB).
*/
public String readableFileSize(long bytes) {
final String[] units = mContext.getResources().getStringArray(R.array.file_size_units);
public static String readableFileSize(Context context, long bytes) {
final String[] units = context.getResources().getStringArray(R.array.file_size_units);
if (bytes <= 0) return "0 " + units[0];
int digitGroups = (int) (Math.log10(bytes)/Math.log10(1024));
return new DecimalFormat("#,##0.#")
@ -431,9 +459,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
/**
* Converts a number of bytes to a human readable transfer rate in bits (eg 100 Kb/s).
*/
public String readableTransferRate(long bytes) {
long bits = bytes * 8;
final String[] units = mContext.getResources().getStringArray(R.array.transfer_rate_units);
public static String readableTransferRate(Context context, long bits) {
final String[] units = context.getResources().getStringArray(R.array.transfer_rate_units);
if (bits <= 0) return "0 " + units[0];
int digitGroups = (int) (Math.log10(bits)/Math.log10(1024));
return new DecimalFormat("#,##0.#")
@ -442,6 +469,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
/**
* Listener for {@link #getConnections}.
*
* NOTE: The parameter connections is cached internally. Do not modify it or
* any of its contents.
*/
public interface OnReceiveConnectionsListener {
public void onReceiveConnections(Map<String, Connection> connections);
@ -456,6 +486,13 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
new GetTask() {
@Override
protected void onPostExecute(String s) {
Long now = System.currentTimeMillis();
Long difference = (now - mPreviousConnectionTime) / 1000;
if (difference < 1) {
listener.onReceiveConnections(mPreviousConnections);
return;
}
try {
JSONObject json = new JSONObject(s);
String[] names = json.names().join(" ").replace("\"", "").split(" ");
@ -469,11 +506,24 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
c.OutBytesTotal = conn.getLong("OutBytesTotal");
c.Address = conn.getString("Address");
c.ClientVersion = conn.getString("ClientVersion");
c.Completion = conn.getDouble("Completion");
c.Completion = conn.getInt("Completion");
Connection prev = (mPreviousConnections.containsKey(address))
? mPreviousConnections.get(address)
: new Connection();
mPreviousConnectionTime = now;
if (difference != 0) {
c.InBits = Math.max(0, 8 *
(conn.getLong("InBytesTotal") - prev.InBytesTotal) / difference);
c.OutBits = Math.max(0, 8 *
(conn.getLong("OutBytesTotal") - prev.OutBytesTotal) / difference);
}
connections.put(address, c);
}
listener.onReceiveConnections(connections);
mPreviousConnections = connections;
listener.onReceiveConnections(mPreviousConnections);
}
catch (JSONException e) {
Log.w(TAG, "Failed to parse connections", e);
@ -482,4 +532,43 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
}.execute(mUrl, GetTask.URI_CONNECTIONS, mApiKey);
}
/**
* Listener for {@link #getModel}.
*/
public interface OnReceiveModelListener {
public void onReceiveModel(String repoId, Model model);
}
/**
* Returns status information about the repo with the given ID.
*/
public void getModel(final String repoId, final OnReceiveModelListener listener) {
new GetTask() {
@Override
protected void onPostExecute(String s) {
try {
JSONObject json = new JSONObject(s);
Model m = new Model();
m.globalBytes = json.getLong("globalBytes");
m.globalDeleted = json.getLong("globalDeleted");
m.globalFiles = json.getLong("globalFiles");
m.localBytes = json.getLong("localBytes");
m.localDeleted = json.getLong("localDeleted");
m.localFiles = json.getLong("localFiles");
m.inSyncBytes = json.getLong("inSyncBytes");
m.inSyncFiles = json.getLong("inSyncFiles");
m.needBytes = json.getLong("needBytes");
m.needFiles = json.getLong("needFiles");
m.state = json.getString("state");
m.invalid = json.optString("invalid");
listener.onReceiveModel(repoId, m);
}
catch (JSONException e) {
Log.w(TAG, "Failed to read repository info", e);
}
}
}.execute(mUrl, GetTask.URI_MODEL, mApiKey, "repo", repoId);
}
}

View file

@ -12,7 +12,6 @@
<TextView
android:id="@+id/node_id_title"
android:textStyle="bold"
android:text="Node ID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@ -30,7 +29,7 @@
android:layout_height="wrap_content" >
<TextView
android:id="@+id/cpu_usage_title"
android:text="CPU Usage"
android:text="@string/cpu_usage"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@ -48,7 +47,7 @@
android:layout_height="wrap_content" >
<TextView
android:id="@+id/ram_usage_title"
android:text="RAM Usage"
android:text="@string/ram_usage"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@ -66,7 +65,7 @@
android:layout_height="wrap_content" >
<TextView
android:id="@+id/download_title"
android:text="Download"
android:text="@string/download_title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@ -84,7 +83,7 @@
android:layout_height="wrap_content" >
<TextView
android:id="@+id/upload_title"
android:text="Upload"
android:text="@string/upload_title"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@ -102,7 +101,7 @@
android:layout_height="wrap_content" >
<TextView
android:id="@+id/announce_server_title"
android:text="Announce Server"
android:text="@string/announce_server"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight" >
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="4dip" >
<TextView
android:id="@+id/name"
@ -11,7 +11,46 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:lines="1"
android:ellipsize="end"
android:padding="4dip" />
android:ellipsize="end" />
</LinearLayout>
<TextView
android:id="@+id/status"
android:layout_alignParentRight="true"
android:layout_alignBottom="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/download_title"
android:text="@string/download_title_colon"
android:layout_below="@id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/download"
android:layout_alignBaseline="@id/download_title"
android:layout_toRightOf="@id/download_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/upload_title"
android:text="@string/upload_title_colon"
android:layout_below="@id/download_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/upload"
android:layout_alignBaseline="@id/upload_title"
android:layout_toRightOf="@id/upload_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight" >
android:layout_height="wrap_content"
android:padding="4dip" >
<TextView
android:id="@+id/id"
@ -11,7 +12,35 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:lines="1"
android:ellipsize="end"
android:padding="4dip" />
android:ellipsize="end" />
</LinearLayout>
<TextView
android:id="@+id/state"
android:textColor="#00ff00"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/folder"
android:layout_below="@id/state"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/progress"
android:layout_below="@id/folder"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/invalid"
android:textColor="@color/text_red"
android:layout_below="@id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="text_red">#ffff4444</color>
<color name="text_blue">#ff33b5e5</color>
<color name="text_green">#ff99cc00</color>
</resources>

View file

@ -25,6 +25,9 @@
<string name="repositories_fragment_title">Repositories</string>
<!-- Format string for repository progress. First parameter is status string, second is sync percentage -->
<string name="repo_progress_format">%1$s (%2$d%%)</string>
<!-- Shown if no repos exist -->
<string name="repositories_list_empty">No repositories found</string>
@ -35,6 +38,38 @@
<!-- Shown if no nodes exist -->
<string name="nodes_list_empty">No nodes found</string>
<!-- Indicates that a repo is fully synced to the local node -->
<string name="node_up_to_date">Up to Date</string>
<!-- Indicates that the node is currently syncing. Parameter is sync percentage -->
<string name="node_syncing">Syncing (%1$d%%)</string>
<!-- Indicates that there is no connection to the node -->
<string name="node_disconnected">Disconnected</string>
<!-- Title for current download rate -->
<string name="download_title">Download</string>
<!-- Title for current upload rate -->
<string name="upload_title">Upload</string>
<!-- LocalNodeInfoFragment -->
<!-- Same as download_title with a colon and space appended -->
<string name="download_title_colon">Download:\u0020</string>
<!-- Same as upload_title with a colon and space appended -->
<string name="upload_title_colon">Upload:\u0020</string>
<!-- Title for current CPU usage -->
<string name="cpu_usage">CPU Usage</string>
<!-- Title for current RAM usage -->
<string name="ram_usage">RAM Usage</string>
<!-- Title for announce server status -->
<string name="announce_server">Announce Server</string>
<!-- WebGuiActivity -->
<!-- Title of the web gui activity -->