diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index a3d78409..0df88aac 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -18,7 +18,7 @@
android:theme="@style/Theme.AppCompat" >
@@ -27,12 +27,20 @@
+
+
+
+
+ android:value=".gui.MainActivity" />
diff --git a/src/main/java/com/nutomic/syncthingandroid/NodeAdapter.java b/src/main/java/com/nutomic/syncthingandroid/NodeAdapter.java
new file mode 100644
index 00000000..2c5e64f8
--- /dev/null
+++ b/src/main/java/com/nutomic/syncthingandroid/NodeAdapter.java
@@ -0,0 +1,45 @@
+package com.nutomic.syncthingandroid;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import com.nutomic.syncthingandroid.syncthing.RestApi;
+
+import java.util.List;
+
+/**
+ * Generates item views for node items.
+ */
+public class NodeAdapter extends ArrayAdapter {
+
+ public NodeAdapter(Context context) {
+ super(context, R.layout.node_list_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.node_list_item, parent, false);
+ }
+ TextView name = (TextView) convertView.findViewById(R.id.name);
+ name.setText(getItem(position).Name);
+ return convertView;
+ }
+
+
+ /**
+ * Replacement for addAll, which is not implemented on lower API levels.
+ */
+ public void add(List nodes) {
+ for (RestApi.Node n : nodes) {
+ add(n);
+ }
+ }
+
+}
diff --git a/src/main/java/com/nutomic/syncthingandroid/RepositoryAdapter.java b/src/main/java/com/nutomic/syncthingandroid/RepositoryAdapter.java
new file mode 100644
index 00000000..d46c820b
--- /dev/null
+++ b/src/main/java/com/nutomic/syncthingandroid/RepositoryAdapter.java
@@ -0,0 +1,45 @@
+package com.nutomic.syncthingandroid;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import com.nutomic.syncthingandroid.syncthing.RestApi;
+
+import java.util.List;
+
+/**
+ * Generates item views for repository items.
+ */
+public class RepositoryAdapter extends ArrayAdapter {
+
+ public RepositoryAdapter(Context context) {
+ super(context, R.layout.node_list_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ convertView = inflater.inflate(R.layout.repository_list_item, parent, false);
+ }
+ TextView id = (TextView) convertView.findViewById(R.id.id);
+ id.setText(getItem(position).ID);
+ return convertView;
+ }
+
+
+ /**
+ * Replacement for addAll, which is not implemented on lower API levels.
+ */
+ public void add(List nodes) {
+ for (RestApi.Repository r : nodes) {
+ add(r);
+ }
+ }
+
+}
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java
new file mode 100644
index 00000000..58255f30
--- /dev/null
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/LoadingListFragment.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.TextView;
+
+import com.nutomic.syncthingandroid.R;
+import com.nutomic.syncthingandroid.syncthing.RestApi;
+import com.nutomic.syncthingandroid.syncthing.SyncthingService;
+
+/**
+ * {@link android.support.v4.app.ListFragment} that shows a configurable loading text.
+ */
+public abstract class LoadingListFragment extends Fragment implements RestApi.OnApiAvailableListener {
+
+ private boolean mInitialized = false;
+
+ private ListFragment mListFragment;
+
+ private View mListFragmentHolder;
+
+ private View mLoading;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+
+
+ if (savedInstanceState != null) {
+ mListFragment = (ListFragment) getChildFragmentManager()
+ .getFragment(savedInstanceState, ListFragment.class.getName());
+ }
+ else {
+ mListFragment = new ListFragment();
+ }
+ getChildFragmentManager()
+ .beginTransaction()
+ .add(R.id.list_fragment, mListFragment)
+ .commit();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.loading_list_fragment, container, false);
+ mListFragmentHolder = view.findViewById(R.id.list_fragment);
+ mLoading = view.findViewById(R.id.loading);
+ TextView loadingTextView = (TextView) view.findViewById(R.id.loading_text);
+
+ if (SyncthingService.isFirstStart(getActivity())) {
+ loadingTextView.setText(getString(R.string.web_gui_creating_key));
+ }
+
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ getChildFragmentManager().putFragment(outState, ListFragment.class.getName(), mListFragment);
+ }
+
+ /**
+ * Sets adapter and empty text for {@link ListFragment}
+ * @param adapter Adapter to be used for {@link}ListFragment#setListAdapter}
+ * @param emptyText Resource id for text to be shown in the
+ * {@link ListFragment#setEmptyText(CharSequence)}.
+ */
+ public void setListAdapter(ListAdapter adapter, int emptyText) {
+ mListFragment.setListAdapter(adapter);
+
+ mLoading.setVisibility(View.INVISIBLE);
+ mListFragmentHolder.setVisibility(View.VISIBLE);
+ mListFragment.setEmptyText(getString(emptyText));
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ onApiAvailable();
+ }
+
+ /**
+ * Calls onInitAdapter if it has not yet been called, ListFragment is initialized,
+ * and SyncthingService is not null, and
+ */
+ @Override
+ public void onApiAvailable() {
+ MainActivity activity = (MainActivity) getActivity();
+ if (!mInitialized && getActivity() != null &&
+ activity.getApi() != null && mListFragment != null) {
+ onInitAdapter(activity);
+ mInitialized = true;
+ }
+ }
+
+ /**
+ * Called when the list adapter should be set.
+ * @param activity
+ */
+ public abstract void onInitAdapter(MainActivity activity);
+
+}
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java
new file mode 100644
index 00000000..f0810b23
--- /dev/null
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java
@@ -0,0 +1,206 @@
+package com.nutomic.syncthingandroid.gui;
+
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBar.Tab;
+import android.support.v7.app.ActionBar.TabListener;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.nutomic.syncthingandroid.R;
+import com.nutomic.syncthingandroid.syncthing.RestApi;
+import com.nutomic.syncthingandroid.syncthing.SyncthingService;
+import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
+
+/**
+ * Shows {@link RepositoriesFragment} and {@link NodesFragment} in different tabs.
+ */
+public class MainActivity extends ActionBarActivity
+ implements SyncthingService.OnWebGuiAvailableListener{
+
+ private SyncthingService mSyncthingService;
+
+ private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
+ mSyncthingService = binder.getService();
+ mSyncthingService.registerOnWebGuiAvailableListener(MainActivity.this);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mSyncthingService = null;
+ }
+ };
+
+ @Override
+ public void onWebGuiAvailable() {
+ mSyncthingService.getApi().registerOnApiAvailableListener(mRepositoriesFragment);
+ mSyncthingService.getApi().registerOnApiAvailableListener(mNodesFragment);
+ }
+
+ private final FragmentStatePagerAdapter mSectionsPagerAdapter =
+ new FragmentStatePagerAdapter(getSupportFragmentManager()) {
+
+ @Override
+ public Fragment getItem(int position) {
+ switch (position) {
+ case 0: return mRepositoriesFragment;
+ case 1: return mNodesFragment;
+ default: return null;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return 2;
+ }
+
+ };
+
+ private RepositoriesFragment mRepositoriesFragment;
+
+ private NodesFragment mNodesFragment;
+
+ ViewPager mViewPager;
+
+ /**
+ * Initializes tab navigation.
+ */
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final ActionBar actionBar = getSupportActionBar();
+
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ setContentView(R.layout.main_activity);
+
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+ mViewPager.setAdapter(mSectionsPagerAdapter);
+ mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ actionBar.setSelectedNavigationItem(position);
+ }
+ });
+
+ TabListener tabListener = new TabListener() {
+ public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ mViewPager.setCurrentItem(tab.getPosition());
+ }
+
+ @Override
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ }
+
+ @Override
+ public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ }
+ };
+
+ actionBar.addTab(actionBar.newTab()
+ .setText(R.string.repositories_fragment_title)
+ .setTabListener(tabListener));
+ actionBar.addTab(actionBar.newTab()
+ .setText(R.string.nodes_fragment_title)
+ .setTabListener(tabListener));
+
+ if (savedInstanceState != null) {
+ FragmentManager fm = getSupportFragmentManager();
+ mRepositoriesFragment = (RepositoriesFragment) fm.getFragment(
+ savedInstanceState, RepositoriesFragment.class.getName());
+ mNodesFragment = (NodesFragment) fm.getFragment(
+ savedInstanceState, NodesFragment.class.getName());
+ mViewPager.setCurrentItem(savedInstanceState.getInt("currentTab"));
+ }
+ else {
+ mRepositoriesFragment = new RepositoriesFragment();
+ mNodesFragment = new NodesFragment();
+ }
+
+ if (SyncthingService.isFirstStart(this)) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.welcome_title)
+ .setMessage(R.string.welcome_text)
+ .setNeutralButton(android.R.string.ok, null)
+ .show();
+ }
+
+ getApplicationContext().startService(
+ new Intent(this, SyncthingService.class));
+ bindService(new Intent(this, SyncthingService.class),
+ mSyncthingServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unbindService(mSyncthingServiceConnection);
+ }
+
+ /**
+ * Saves fragment states.
+ */
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ // Avoid crash if called during startup.
+ if (mRepositoriesFragment != null && mNodesFragment != null) {
+ FragmentManager fm = getSupportFragmentManager();
+ fm.putFragment(outState, RepositoriesFragment.class.getName(), mRepositoriesFragment);
+ fm.putFragment(outState, NodesFragment.class.getName(), mNodesFragment);
+ outState.putInt("currentTab", mViewPager.getCurrentItem());
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ return mSyncthingService != null && mSyncthingService.isWebGuiAvailable();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.web_gui:
+ startActivity(new Intent(this, WebGuiActivity.class));
+ return true;
+ case R.id.settings:
+ startActivity(new Intent(this, SettingsActivity.class));
+ return true;
+ case R.id.exit:
+ // Make sure we unbind first.
+ finish();
+ getApplicationContext().stopService(new Intent(this, SyncthingService.class));
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Returns RestApi instance, or null if SyncthingService is not yet connected.
+ */
+ public RestApi getApi() {
+ return (mSyncthingService != null)
+ ? mSyncthingService.getApi()
+ : null;
+ }
+
+}
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java
new file mode 100644
index 00000000..addd4e32
--- /dev/null
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/NodesFragment.java
@@ -0,0 +1,20 @@
+package com.nutomic.syncthingandroid.gui;
+
+import com.nutomic.syncthingandroid.NodeAdapter;
+import com.nutomic.syncthingandroid.R;
+import com.nutomic.syncthingandroid.syncthing.RestApi;
+
+/**
+ * Displays a list of all existing nodes.
+ */
+public class NodesFragment extends LoadingListFragment implements
+ RestApi.OnApiAvailableListener {
+
+ @Override
+ public void onInitAdapter(MainActivity activity) {
+ NodeAdapter adapter = new NodeAdapter(activity);
+ adapter.add(activity.getApi().getNodes());
+ setListAdapter(adapter, R.string.nodes_list_empty);
+ }
+
+}
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java b/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java
new file mode 100644
index 00000000..58b194c4
--- /dev/null
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/RepositoriesFragment.java
@@ -0,0 +1,20 @@
+package com.nutomic.syncthingandroid.gui;
+
+import com.nutomic.syncthingandroid.R;
+import com.nutomic.syncthingandroid.RepositoryAdapter;
+import com.nutomic.syncthingandroid.syncthing.RestApi;
+
+/**
+ * Displays a list of all existing repositories.
+ */
+public class RepositoriesFragment extends LoadingListFragment implements
+ RestApi.OnApiAvailableListener {
+
+ @Override
+ public void onInitAdapter(MainActivity activity) {
+ RepositoryAdapter adapter = new RepositoryAdapter(activity);
+ adapter.add(activity.getApi().getRepositories());
+ setListAdapter(adapter, R.string.repositories_list_empty);
+ }
+
+}
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java
index f0d4ba3a..33bb0051 100644
--- a/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/SettingsActivity.java
@@ -5,7 +5,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -43,7 +42,7 @@ public class SettingsActivity extends PreferenceActivity
/**
* Binds to service and sets syncthing preferences from Rest API.
*/
- private ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
+ private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/WebGuiActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/WebGuiActivity.java
index 2407a997..9456e2a8 100644
--- a/src/main/java/com/nutomic/syncthingandroid/gui/WebGuiActivity.java
+++ b/src/main/java/com/nutomic/syncthingandroid/gui/WebGuiActivity.java
@@ -1,16 +1,13 @@
package com.nutomic.syncthingandroid.gui;
import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
-import android.view.Menu;
-import android.view.MenuItem;
+import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -24,16 +21,15 @@ import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
/**
* Holds a WebView that shows the web ui of the local syncthing instance.
*/
-public class WebGuiActivity extends Activity implements SyncthingService.OnWebGuiAvailableListener {
-
- private static final String TAG = "WebGuiActivity";
+public class WebGuiActivity extends ActionBarActivity implements SyncthingService.OnWebGuiAvailableListener {
private WebView mWebView;
+
private View mLoadingView;
private SyncthingService mSyncthingService;
- private ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
+ private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service;
@@ -49,7 +45,7 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
/**
* Hides the loading screen and shows the WebView once it is fully loaded.
*/
- private WebViewClient mWebViewClient = new WebViewClient() {
+ private final WebViewClient mWebViewClient = new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
@@ -68,7 +64,8 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
+ setContentView(R.layout.web_gui_activity);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mLoadingView = findViewById(R.id.loading);
ProgressBar pb = (ProgressBar) mLoadingView.findViewById(R.id.progress);
@@ -81,15 +78,8 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
if (SyncthingService.isFirstStart(this)) {
TextView loadingText = (TextView) mLoadingView.findViewById(R.id.loading_text);
loadingText.setText(R.string.web_gui_creating_key);
- new AlertDialog.Builder(this)
- .setTitle(R.string.welcome_title)
- .setMessage(R.string.welcome_text)
- .setNeutralButton(android.R.string.ok, null)
- .show();
}
- getApplicationContext().startService(
- new Intent(this, SyncthingService.class));
bindService(
new Intent(this, SyncthingService.class),
mSyncthingServiceConnection, Context.BIND_AUTO_CREATE);
@@ -108,32 +98,5 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
super.onDestroy();
unbindService(mSyncthingServiceConnection);
}
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.menu, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- return mSyncthingService != null && mSyncthingService.isWebGuiAvailable();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.settings:
- startActivity(new Intent(this, SettingsActivity.class));
- return true;
- case R.id.exit:
- // Make sure we unbind first.
- finish();
- getApplicationContext().stopService(new Intent(this, SyncthingService.class));
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
}
diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java
index e0aba1d9..939aa114 100644
--- a/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java
+++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/GetTask.java
@@ -39,6 +39,7 @@ public class GetTask extends AsyncTask {
HttpClient httpclient = new DefaultHttpClient();
HttpGet get = new HttpGet(fullUri);
get.addHeader(new BasicHeader("X-API-Key", params[2]));
+
try {
HttpResponse response = httpclient.execute(get);
HttpEntity entity = response.getEntity();
diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java
index f4728573..ea3aef65 100644
--- a/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java
+++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/PostTask.java
@@ -3,7 +3,6 @@ package com.nutomic.syncthingandroid.syncthing;
import android.os.AsyncTask;
import android.util.Log;
-import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
@@ -38,6 +37,7 @@ public class PostTask extends AsyncTask {
HttpClient httpclient = new DefaultHttpClient();
HttpPost post = new HttpPost(fullUri);
post.addHeader(new BasicHeader("X-API-Key", params[2]));
+
try {
post.setEntity(new StringEntity(params[3]));
httpclient.execute(post);
diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java
index add8630b..d635ecd5 100644
--- a/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java
+++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/RestApi.java
@@ -14,6 +14,12 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
/**
* Provides functions to interact with the syncthing REST API.
*/
@@ -31,19 +37,62 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
*/
public static final String TYPE_GUI = "GUI";
+ public static class Node {
+ public String Addresses;
+ public String Name;
+ public String NodeID;
+ }
+
+ public static class Repository {
+ public String Directory;
+ public String ID;
+ public final boolean IgnorePerms = true;
+ public String Invalid;
+ public List Nodes;
+ public boolean ReadOnly;
+ public Versioning Versioning;
+ }
+
+ public static class Versioning {
+ protected final Map mParams = new HashMap();
+ public String getType() {
+ return "";
+ }
+ public Map getParams() {
+ return mParams;
+ }
+ }
+
+ public static class SimpleVersioning extends Versioning {
+ @Override
+ public String getType() {
+ return "simple";
+ }
+ public void setParams(int keep) {
+ mParams.put("keep", Integer.toString(keep));
+ }
+ }
+
+ public interface OnApiAvailableListener {
+ public void onApiAvailable();
+ }
+
+ private final LinkedList mOnApiAvailableListeners =
+ new LinkedList();
+
private static final int NOTIFICATION_RESTART = 2;
- private Context mContext;
+ private final Context mContext;
private String mVersion;
- private String mUrl;
+ private final String mUrl;
private String mApiKey;
private JSONObject mConfig;
- private NotificationManager mNotificationManager;
+ private final NotificationManager mNotificationManager;
public RestApi(Context context, String url, String apiKey) {
mContext = context;
@@ -60,6 +109,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
return mUrl;
}
+ /**
+ * Gets version and config, then calls any OnApiAvailableListeners.
+ */
@Override
public void onWebGuiAvailable() {
new GetTask() {
@@ -74,6 +126,10 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
protected void onPostExecute(String config) {
try {
mConfig = new JSONObject(config);
+ for (OnApiAvailableListener listener : mOnApiAvailableListeners) {
+ listener.onApiAvailable();
+ }
+ mOnApiAvailableListeners.clear();
}
catch (JSONException e) {
Log.w(TAG, "Failed to parse config", e);
@@ -139,16 +195,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
*/
public void setValue(String name, String key, T value, boolean isArray) {
try {
- if (isArray) {
- JSONArray json = new JSONArray();
- for (String s : ((String) value).split(" ")) {
- json.put(s);
- }
- mConfig.getJSONObject(name).put(key, json);
- }
- else {
- mConfig.getJSONObject(name).put(key, value);
- }
+ mConfig.getJSONObject(name).put(key, (isArray)
+ ? listToJson(((String) value).split(" "))
+ : value);
configUpdated();
}
catch (JSONException e) {
@@ -156,6 +205,18 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
}
}
+ /**
+ * Converts an array of strings to JSON array. Like JSONArray#JSONArray(Object array), but
+ * works on all API levels.
+ */
+ private JSONArray listToJson(String[] list) {
+ JSONArray json = new JSONArray();
+ for (String s : list) {
+ json.put(s);
+ }
+ return json;
+ }
+
/**
* Sends the updated mConfig via Rest API to syncthing and displays a "restart" notification.
*/
@@ -176,4 +237,90 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
mNotificationManager.notify(NOTIFICATION_RESTART, n);
}
+ /**
+ * Returns a list of all existing nodes.
+ */
+ public List getNodes() {
+ try {
+ return getNodes(mConfig.getJSONArray("Nodes"));
+ }
+ catch (JSONException e) {
+ Log.w(TAG, "Failed to read nodes", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of all nodes in the array nodes.
+ */
+ private List getNodes(JSONArray nodes) throws JSONException {
+ List ret;
+ ret = new ArrayList(nodes.length());
+ for (int i = 0; i < nodes.length(); i++) {
+ JSONObject json = nodes.getJSONObject(i);
+ Node n = new Node();
+ if (!json.isNull("Addresses")) {
+ n.Addresses = json.getJSONArray("Addresses").join(" ").replace("\"", "");
+ }
+ n.Name = json.getString("Name");
+ n.NodeID = json.getString("NodeID");
+ ret.add(n);
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all existing repositores.
+ */
+ public List getRepositories() {
+ List ret = null;
+ try {
+ JSONArray repos = mConfig.getJSONArray("Repositories");
+ ret = new ArrayList(repos.length());
+ for (int i = 0; i < repos.length(); i++) {
+ JSONObject json = repos.getJSONObject(i);
+ Repository r = new Repository();
+ r.Directory = json.getString("Directory");
+ r.ID = json.getString("ID");
+ // Hardcoded to true because missing permissions support.
+ // r.IgnorePerms = json.getBoolean("IgnorePerms");
+ r.Invalid = json.getString("Invalid");
+ r.Nodes = getNodes(json.getJSONArray("Nodes"));
+
+ r.ReadOnly = json.getBoolean("ReadOnly");
+ JSONObject versioning = json.getJSONObject("Versioning");
+ if (versioning.getString("Type").equals("simple")) {
+ SimpleVersioning sv = new SimpleVersioning();
+ JSONObject params = versioning.getJSONObject("Params");
+ sv.setParams(params.getInt("keep"));
+ r.Versioning = sv;
+ }
+ else {
+ r.Versioning = new Versioning();
+ }
+
+ ret.add(r);
+ }
+ }
+ catch (JSONException e) {
+ Log.w(TAG, "Failed to read nodes", e);
+ }
+ return ret;
+ }
+
+ /**
+ * Register a listener for the web gui becoming available..
+ *
+ * If the web gui is already available, listener will be called immediately.
+ * Listeners are unregistered automatically after being called.
+ */
+ public void registerOnApiAvailableListener(OnApiAvailableListener listener) {
+ if (mConfig != null) {
+ listener.onApiAvailable();
+ }
+ else {
+ mOnApiAvailableListeners.addLast(listener);
+ }
+ }
+
}
diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java
index b3aaeca9..90b9b9b2 100644
--- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java
+++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingService.java
@@ -14,7 +14,7 @@ import android.util.Log;
import android.util.Pair;
import com.nutomic.syncthingandroid.R;
-import com.nutomic.syncthingandroid.gui.WebGuiActivity;
+import com.nutomic.syncthingandroid.gui.MainActivity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
@@ -104,7 +104,7 @@ public class SyncthingService extends Service {
public void onWebGuiAvailable();
}
- private LinkedList mOnWebGuiAvailableListeners =
+ private final LinkedList mOnWebGuiAvailableListeners =
new LinkedList();
private boolean mIsWebGuiAvailable = false;
@@ -292,10 +292,9 @@ public class SyncthingService extends Service {
* Creates notification, starts native binary.
*/
@Override
-
public void onCreate() {
PendingIntent pi = PendingIntent.getActivity(
- this, 0, new Intent(this, WebGuiActivity.class),
+ this, 0, new Intent(this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
Notification n = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
@@ -498,7 +497,7 @@ public class SyncthingService extends Service {
in = getResources().openRawResource(R.raw.config_default);
out = new FileOutputStream(getConfigFile());
byte[] buff = new byte[1024];
- int read = 0;
+ int read;
while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read);
diff --git a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingServiceBinder.java b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingServiceBinder.java
index a9b3a375..b1a56224 100644
--- a/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingServiceBinder.java
+++ b/src/main/java/com/nutomic/syncthingandroid/syncthing/SyncthingServiceBinder.java
@@ -4,7 +4,7 @@ import android.os.Binder;
public class SyncthingServiceBinder extends Binder {
- SyncthingService mService;
+ private final SyncthingService mService;
public SyncthingServiceBinder(SyncthingService service) {
mService = service;
diff --git a/src/main/res/layout/loading_list_fragment.xml b/src/main/res/layout/loading_list_fragment.xml
new file mode 100644
index 00000000..3aeb0bb6
--- /dev/null
+++ b/src/main/res/layout/loading_list_fragment.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/main_activity.xml b/src/main/res/layout/main_activity.xml
new file mode 100644
index 00000000..31185efc
--- /dev/null
+++ b/src/main/res/layout/main_activity.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/main/res/layout/node_list_item.xml b/src/main/res/layout/node_list_item.xml
new file mode 100644
index 00000000..54772689
--- /dev/null
+++ b/src/main/res/layout/node_list_item.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/repository_list_item.xml b/src/main/res/layout/repository_list_item.xml
new file mode 100644
index 00000000..1609afe3
--- /dev/null
+++ b/src/main/res/layout/repository_list_item.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/main.xml b/src/main/res/layout/web_gui_activity.xml
similarity index 100%
rename from src/main/res/layout/main.xml
rename to src/main/res/layout/web_gui_activity.xml
diff --git a/src/main/res/menu/menu.xml b/src/main/res/menu/menu.xml
index 6131052c..6372450a 100644
--- a/src/main/res/menu/menu.xml
+++ b/src/main/res/menu/menu.xml
@@ -1,6 +1,10 @@