mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-11-26 06:11:19 +00:00
Added native main activity with tabs for repos and nodes.
This commit is contained in:
parent
7e69c3a354
commit
d524461634
22 changed files with 792 additions and 112 deletions
|
@ -18,7 +18,7 @@
|
|||
android:theme="@style/Theme.AppCompat" >
|
||||
|
||||
<activity
|
||||
android:name=".gui.WebGuiActivity"
|
||||
android:name=".gui.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop" >
|
||||
<intent-filter>
|
||||
|
@ -27,12 +27,20 @@
|
|||
</intent-filter>
|
||||
</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
|
||||
android:name=".gui.SettingsActivity"
|
||||
android:label="@string/settings_title" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".WebGuiActivity" />
|
||||
android:value=".gui.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<service android:name=".syncthing.SyncthingService" />
|
||||
|
|
45
src/main/java/com/nutomic/syncthingandroid/NodeAdapter.java
Normal file
45
src/main/java/com/nutomic/syncthingandroid/NodeAdapter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
206
src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java
Normal file
206
src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ public class GetTask extends AsyncTask<String, Void, String> {
|
|||
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();
|
||||
|
|
|
@ -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<String, Void, Void> {
|
|||
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);
|
||||
|
|
|
@ -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<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 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 <T> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
|
||||
private final LinkedList<OnWebGuiAvailableListener> mOnWebGuiAvailableListeners =
|
||||
new LinkedList<OnWebGuiAvailableListener>();
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
39
src/main/res/layout/loading_list_fragment.xml
Normal file
39
src/main/res/layout/loading_list_fragment.xml
Normal 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>
|
7
src/main/res/layout/main_activity.xml
Normal file
7
src/main/res/layout/main_activity.xml
Normal 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" />
|
17
src/main/res/layout/node_list_item.xml
Normal file
17
src/main/res/layout/node_list_item.xml
Normal 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>
|
17
src/main/res/layout/repository_list_item.xml
Normal file
17
src/main/res/layout/repository_list_item.xml
Normal 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>
|
|
@ -1,6 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/web_gui"
|
||||
android:title="@string/web_gui_title" />
|
||||
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:title="@string/settings_title" />
|
||||
|
|
|
@ -9,8 +9,25 @@
|
|||
<!-- Text of the notification shown when a restart is needed -->
|
||||
<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 -->
|
||||
|
||||
<!-- Title of the web gui activity -->
|
||||
<string name="web_gui_title">Web GUI</string>
|
||||
|
||||
<!-- Text for WebGuiActivity loading view -->
|
||||
<string name="web_gui_loading">Waiting for GUI</string>
|
||||
|
||||
|
|
|
@ -2,79 +2,79 @@
|
|||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="syncthing_options"
|
||||
android:title="Syncthing Options"
|
||||
android:persistent="false" >
|
||||
android:key="syncthing_options"
|
||||
android:title="Syncthing Options"
|
||||
android:persistent="false" >
|
||||
|
||||
<EditTextPreference
|
||||
android:key="ListenAddress"
|
||||
android:title="Sync Protocol Listen Addresses"
|
||||
android:defaultValue="0.0.0.0:22000" />
|
||||
android:key="ListenAddress"
|
||||
android:title="Sync Protocol Listen Addresses"
|
||||
android:defaultValue="0.0.0.0:22000" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="MaxSendKbps"
|
||||
android:title="Outgoing Rate Limit (KiB/s)"
|
||||
android:numeric="integer" />
|
||||
android:key="MaxSendKbps"
|
||||
android:title="Outgoing Rate Limit (KiB/s)"
|
||||
android:numeric="integer" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="RescanIntervalS"
|
||||
android:title="Rescan Interval (s)"
|
||||
android:numeric="integer" />
|
||||
android:key="RescanIntervalS"
|
||||
android:title="Rescan Interval (s)"
|
||||
android:numeric="integer" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="ReconnectIntervalS"
|
||||
android:title="Reconnect Interval (s)"
|
||||
android:numeric="integer" />
|
||||
android:key="ReconnectIntervalS"
|
||||
android:title="Reconnect Interval (s)"
|
||||
android:numeric="integer" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="ParallelRequests"
|
||||
android:title="Max Outstanding Requests"
|
||||
android:numeric="integer" />
|
||||
android:key="ParallelRequests"
|
||||
android:title="Max Outstanding Requests"
|
||||
android:numeric="integer" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="MaxChangeKbps"
|
||||
android:title="Max File Change Rate (KiB/s)"
|
||||
android:numeric="integer" />
|
||||
android:key="MaxChangeKbps"
|
||||
android:title="Max File Change Rate (KiB/s)"
|
||||
android:numeric="integer" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="GlobalAnnEnabled"
|
||||
android:title="Global Discovery" />
|
||||
android:key="GlobalAnnEnabled"
|
||||
android:title="Global Discovery" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="LocalAnnEnabled"
|
||||
android:title="Local Discovery" />
|
||||
android:key="LocalAnnEnabled"
|
||||
android:title="Local Discovery" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="LocalAnnPort"
|
||||
android:title="Local Discovery Port"
|
||||
android:numeric="integer" />
|
||||
android:key="LocalAnnPort"
|
||||
android:title="Local Discovery Port"
|
||||
android:numeric="integer" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="UPnPEnabled"
|
||||
android:title="Enable UPnP" />
|
||||
android:key="UPnPEnabled"
|
||||
android:title="Enable UPnP" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="syncthing_gui"
|
||||
android:title="Syncthing GUI"
|
||||
android:persistent="false" >
|
||||
android:key="syncthing_gui"
|
||||
android:title="Syncthing GUI"
|
||||
android:persistent="false" >
|
||||
|
||||
<EditTextPreference
|
||||
android:key="Address"
|
||||
android:title="GUI Listen Addresses" />
|
||||
android:key="Address"
|
||||
android:title="GUI Listen Addresses" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="User"
|
||||
android:title="GUI Authentication User" />
|
||||
android:key="User"
|
||||
android:title="GUI Authentication User" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="Password"
|
||||
android:title="GUI Authentication Password" />
|
||||
android:key="Password"
|
||||
android:title="GUI Authentication Password" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="UseTLS"
|
||||
android:title="Use HTTPS for GUI" />
|
||||
android:key="UseTLS"
|
||||
android:title="Use HTTPS for GUI" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
|
@ -87,8 +87,8 @@
|
|||
</Preference>
|
||||
|
||||
<Preference
|
||||
android:key="syncthing_version"
|
||||
android:title="@string/syncthing_version_title"
|
||||
style="?android:preferenceInformationStyle" />
|
||||
android:key="syncthing_version"
|
||||
android:title="@string/syncthing_version_title"
|
||||
style="?android:preferenceInformationStyle" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
Loading…
Reference in a new issue