1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-11-22 20:31:16 +00:00

Added native main activity with tabs for repos and nodes.

This commit is contained in:
Felix Ableitner 2014-06-03 17:42:17 +02:00
parent 7e69c3a354
commit d524461634
22 changed files with 792 additions and 112 deletions

View file

@ -18,7 +18,7 @@
android:theme="@style/Theme.AppCompat" > android:theme="@style/Theme.AppCompat" >
<activity <activity
android:name=".gui.WebGuiActivity" android:name=".gui.MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" > android:launchMode="singleTop" >
<intent-filter> <intent-filter>
@ -27,12 +27,20 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".gui.WebGuiActivity"
android:label="@string/web_gui_title" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".gui.MainActivity" />
</activity>
<activity <activity
android:name=".gui.SettingsActivity" android:name=".gui.SettingsActivity"
android:label="@string/settings_title" > android:label="@string/settings_title" >
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".WebGuiActivity" /> android:value=".gui.MainActivity" />
</activity> </activity>
<service android:name=".syncthing.SyncthingService" /> <service android:name=".syncthing.SyncthingService" />

View file

@ -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<RestApi.Node> {
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<RestApi.Node> nodes) {
for (RestApi.Node n : nodes) {
add(n);
}
}
}

View file

@ -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<RestApi.Repository> {
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<RestApi.Repository> nodes) {
for (RestApi.Repository r : nodes) {
add(r);
}
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -5,7 +5,6 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@ -43,7 +42,7 @@ public class SettingsActivity extends PreferenceActivity
/** /**
* Binds to service and sets syncthing preferences from Rest API. * 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) { public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) service; SyncthingServiceBinder binder = (SyncthingServiceBinder) service;

View file

@ -1,16 +1,13 @@
package com.nutomic.syncthingandroid.gui; package com.nutomic.syncthingandroid.gui;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.view.Menu; import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; 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. * Holds a WebView that shows the web ui of the local syncthing instance.
*/ */
public class WebGuiActivity extends Activity implements SyncthingService.OnWebGuiAvailableListener { public class WebGuiActivity extends ActionBarActivity implements SyncthingService.OnWebGuiAvailableListener {
private static final String TAG = "WebGuiActivity";
private WebView mWebView; private WebView mWebView;
private View mLoadingView; private View mLoadingView;
private SyncthingService mSyncthingService; private SyncthingService mSyncthingService;
private ServiceConnection mSyncthingServiceConnection = new ServiceConnection() { private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
SyncthingServiceBinder binder = (SyncthingServiceBinder) 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. * 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 @Override
public void onPageFinished(WebView view, String url) { public void onPageFinished(WebView view, String url) {
@ -68,7 +64,8 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.main); setContentView(R.layout.web_gui_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mLoadingView = findViewById(R.id.loading); mLoadingView = findViewById(R.id.loading);
ProgressBar pb = (ProgressBar) mLoadingView.findViewById(R.id.progress); ProgressBar pb = (ProgressBar) mLoadingView.findViewById(R.id.progress);
@ -81,15 +78,8 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
if (SyncthingService.isFirstStart(this)) { if (SyncthingService.isFirstStart(this)) {
TextView loadingText = (TextView) mLoadingView.findViewById(R.id.loading_text); TextView loadingText = (TextView) mLoadingView.findViewById(R.id.loading_text);
loadingText.setText(R.string.web_gui_creating_key); 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( bindService(
new Intent(this, SyncthingService.class), new Intent(this, SyncthingService.class),
mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); mSyncthingServiceConnection, Context.BIND_AUTO_CREATE);
@ -109,31 +99,4 @@ public class WebGuiActivity extends Activity implements SyncthingService.OnWebGu
unbindService(mSyncthingServiceConnection); 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);
}
}
} }

View file

@ -39,6 +39,7 @@ public class GetTask extends AsyncTask<String, Void, String> {
HttpClient httpclient = new DefaultHttpClient(); HttpClient httpclient = new DefaultHttpClient();
HttpGet get = new HttpGet(fullUri); HttpGet get = new HttpGet(fullUri);
get.addHeader(new BasicHeader("X-API-Key", params[2])); get.addHeader(new BasicHeader("X-API-Key", params[2]));
try { try {
HttpResponse response = httpclient.execute(get); HttpResponse response = httpclient.execute(get);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();

View file

@ -3,7 +3,6 @@ package com.nutomic.syncthingandroid.syncthing;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
@ -38,6 +37,7 @@ public class PostTask extends AsyncTask<String, Void, Void> {
HttpClient httpclient = new DefaultHttpClient(); HttpClient httpclient = new DefaultHttpClient();
HttpPost post = new HttpPost(fullUri); HttpPost post = new HttpPost(fullUri);
post.addHeader(new BasicHeader("X-API-Key", params[2])); post.addHeader(new BasicHeader("X-API-Key", params[2]));
try { try {
post.setEntity(new StringEntity(params[3])); post.setEntity(new StringEntity(params[3]));
httpclient.execute(post); httpclient.execute(post);

View file

@ -14,6 +14,12 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; 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. * 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 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<Node> Nodes;
public boolean ReadOnly;
public Versioning Versioning;
}
public static class Versioning {
protected final Map<String, String> mParams = new HashMap<String, String>();
public String getType() {
return "";
}
public Map<String, String> 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<OnApiAvailableListener> mOnApiAvailableListeners =
new LinkedList<OnApiAvailableListener>();
private static final int NOTIFICATION_RESTART = 2; private static final int NOTIFICATION_RESTART = 2;
private Context mContext; private final Context mContext;
private String mVersion; private String mVersion;
private String mUrl; private final String mUrl;
private String mApiKey; private String mApiKey;
private JSONObject mConfig; private JSONObject mConfig;
private NotificationManager mNotificationManager; private final NotificationManager mNotificationManager;
public RestApi(Context context, String url, String apiKey) { public RestApi(Context context, String url, String apiKey) {
mContext = context; mContext = context;
@ -60,6 +109,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
return mUrl; return mUrl;
} }
/**
* Gets version and config, then calls any OnApiAvailableListeners.
*/
@Override @Override
public void onWebGuiAvailable() { public void onWebGuiAvailable() {
new GetTask() { new GetTask() {
@ -74,6 +126,10 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
protected void onPostExecute(String config) { protected void onPostExecute(String config) {
try { try {
mConfig = new JSONObject(config); mConfig = new JSONObject(config);
for (OnApiAvailableListener listener : mOnApiAvailableListeners) {
listener.onApiAvailable();
}
mOnApiAvailableListeners.clear();
} }
catch (JSONException e) { catch (JSONException e) {
Log.w(TAG, "Failed to parse config", e); Log.w(TAG, "Failed to parse config", e);
@ -139,16 +195,9 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
*/ */
public <T> void setValue(String name, String key, T value, boolean isArray) { public <T> void setValue(String name, String key, T value, boolean isArray) {
try { try {
if (isArray) { mConfig.getJSONObject(name).put(key, (isArray)
JSONArray json = new JSONArray(); ? listToJson(((String) value).split(" "))
for (String s : ((String) value).split(" ")) { : value);
json.put(s);
}
mConfig.getJSONObject(name).put(key, json);
}
else {
mConfig.getJSONObject(name).put(key, value);
}
configUpdated(); configUpdated();
} }
catch (JSONException e) { 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. * 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); mNotificationManager.notify(NOTIFICATION_RESTART, n);
} }
/**
* Returns a list of all existing nodes.
*/
public List<Node> 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<Node> getNodes(JSONArray nodes) throws JSONException {
List<Node> ret;
ret = new ArrayList<Node>(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<Repository> getRepositories() {
List<Repository> ret = null;
try {
JSONArray repos = mConfig.getJSONArray("Repositories");
ret = new ArrayList<Repository>(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);
}
}
} }

View file

@ -14,7 +14,7 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.nutomic.syncthingandroid.R; 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.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
@ -104,7 +104,7 @@ public class SyncthingService extends Service {
public void onWebGuiAvailable(); public void onWebGuiAvailable();
} }
private LinkedList<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners = private final LinkedList<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
new LinkedList<OnWebGuiAvailableListener>(); new LinkedList<OnWebGuiAvailableListener>();
private boolean mIsWebGuiAvailable = false; private boolean mIsWebGuiAvailable = false;
@ -292,10 +292,9 @@ public class SyncthingService extends Service {
* Creates notification, starts native binary. * Creates notification, starts native binary.
*/ */
@Override @Override
public void onCreate() { public void onCreate() {
PendingIntent pi = PendingIntent.getActivity( PendingIntent pi = PendingIntent.getActivity(
this, 0, new Intent(this, WebGuiActivity.class), this, 0, new Intent(this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
Notification n = new NotificationCompat.Builder(this) Notification n = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
@ -498,7 +497,7 @@ public class SyncthingService extends Service {
in = getResources().openRawResource(R.raw.config_default); in = getResources().openRawResource(R.raw.config_default);
out = new FileOutputStream(getConfigFile()); out = new FileOutputStream(getConfigFile());
byte[] buff = new byte[1024]; byte[] buff = new byte[1024];
int read = 0; int read;
while ((read = in.read(buff)) > 0) { while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read); out.write(buff, 0, read);

View file

@ -4,7 +4,7 @@ import android.os.Binder;
public class SyncthingServiceBinder extends Binder { public class SyncthingServiceBinder extends Binder {
SyncthingService mService; private final SyncthingService mService;
public SyncthingServiceBinder(SyncthingService service) { public SyncthingServiceBinder(SyncthingService service) {
mService = service; mService = service;

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/list_fragment"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ProgressBar
android:id="@+id/progress"
android:layout_centerInParent="true"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dip" />
<TextView
android:id="@+id/loading_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_below="@id/progress"
android:text="@string/web_gui_loading" />
</RelativeLayout>
</LinearLayout>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight" >
<TextView
android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:lines="1"
android:ellipsize="end"
android:padding="4dip" />
</LinearLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight" >
<TextView
android:id="@+id/id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:lines="1"
android:ellipsize="end"
android:padding="4dip" />
</LinearLayout>

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/web_gui"
android:title="@string/web_gui_title" />
<item <item
android:id="@+id/settings" android:id="@+id/settings"
android:title="@string/settings_title" /> android:title="@string/settings_title" />

View file

@ -9,8 +9,25 @@
<!-- Text of the notification shown when a restart is needed --> <!-- Text of the notification shown when a restart is needed -->
<string name="restart_notif_text">Click here to restart syncthing now</string> <string name="restart_notif_text">Click here to restart syncthing now</string>
<!-- RepositoriesFragment -->
<string name="repositories_fragment_title">Repositories</string>
<!-- Shown if no repos exist -->
<string name="repositories_list_empty">No repositories found</string>
<!-- NodesFragment -->
<string name="nodes_fragment_title">Nodes</string>
<!-- Shown if no nodes exist -->
<string name="nodes_list_empty">No nodes found</string>
<!-- WebGuiActivity --> <!-- WebGuiActivity -->
<!-- Title of the web gui activity -->
<string name="web_gui_title">Web GUI</string>
<!-- Text for WebGuiActivity loading view --> <!-- Text for WebGuiActivity loading view -->
<string name="web_gui_loading">Waiting for GUI</string> <string name="web_gui_loading">Waiting for GUI</string>

View file

@ -2,79 +2,79 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen <PreferenceScreen
android:key="syncthing_options" android:key="syncthing_options"
android:title="Syncthing Options" android:title="Syncthing Options"
android:persistent="false" > android:persistent="false" >
<EditTextPreference <EditTextPreference
android:key="ListenAddress" android:key="ListenAddress"
android:title="Sync Protocol Listen Addresses" android:title="Sync Protocol Listen Addresses"
android:defaultValue="0.0.0.0:22000" /> android:defaultValue="0.0.0.0:22000" />
<EditTextPreference <EditTextPreference
android:key="MaxSendKbps" android:key="MaxSendKbps"
android:title="Outgoing Rate Limit (KiB/s)" android:title="Outgoing Rate Limit (KiB/s)"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="RescanIntervalS" android:key="RescanIntervalS"
android:title="Rescan Interval (s)" android:title="Rescan Interval (s)"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="ReconnectIntervalS" android:key="ReconnectIntervalS"
android:title="Reconnect Interval (s)" android:title="Reconnect Interval (s)"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="ParallelRequests" android:key="ParallelRequests"
android:title="Max Outstanding Requests" android:title="Max Outstanding Requests"
android:numeric="integer" /> android:numeric="integer" />
<EditTextPreference <EditTextPreference
android:key="MaxChangeKbps" android:key="MaxChangeKbps"
android:title="Max File Change Rate (KiB/s)" android:title="Max File Change Rate (KiB/s)"
android:numeric="integer" /> android:numeric="integer" />
<CheckBoxPreference <CheckBoxPreference
android:key="GlobalAnnEnabled" android:key="GlobalAnnEnabled"
android:title="Global Discovery" /> android:title="Global Discovery" />
<CheckBoxPreference <CheckBoxPreference
android:key="LocalAnnEnabled" android:key="LocalAnnEnabled"
android:title="Local Discovery" /> android:title="Local Discovery" />
<EditTextPreference <EditTextPreference
android:key="LocalAnnPort" android:key="LocalAnnPort"
android:title="Local Discovery Port" android:title="Local Discovery Port"
android:numeric="integer" /> android:numeric="integer" />
<CheckBoxPreference <CheckBoxPreference
android:key="UPnPEnabled" android:key="UPnPEnabled"
android:title="Enable UPnP" /> android:title="Enable UPnP" />
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen <PreferenceScreen
android:key="syncthing_gui" android:key="syncthing_gui"
android:title="Syncthing GUI" android:title="Syncthing GUI"
android:persistent="false" > android:persistent="false" >
<EditTextPreference <EditTextPreference
android:key="Address" android:key="Address"
android:title="GUI Listen Addresses" /> android:title="GUI Listen Addresses" />
<EditTextPreference <EditTextPreference
android:key="User" android:key="User"
android:title="GUI Authentication User" /> android:title="GUI Authentication User" />
<EditTextPreference <EditTextPreference
android:key="Password" android:key="Password"
android:title="GUI Authentication Password" /> android:title="GUI Authentication Password" />
<CheckBoxPreference <CheckBoxPreference
android:key="UseTLS" android:key="UseTLS"
android:title="Use HTTPS for GUI" /> android:title="Use HTTPS for GUI" />
</PreferenceScreen> </PreferenceScreen>
@ -87,8 +87,8 @@
</Preference> </Preference>
<Preference <Preference
android:key="syncthing_version" android:key="syncthing_version"
android:title="@string/syncthing_version_title" android:title="@string/syncthing_version_title"
style="?android:preferenceInformationStyle" /> style="?android:preferenceInformationStyle" />
</PreferenceScreen> </PreferenceScreen>