diff --git a/build.gradle b/build.gradle index f8d79b67..701103fe 100644 --- a/build.gradle +++ b/build.gradle @@ -95,7 +95,7 @@ def getVersionCodeFromManifest() { return Integer.parseInt(matcher.group(1)) } -task buildNative(type:Exec) { +task buildNative(type: Exec) { outputs.upToDateWhen { false } executable = './build-native.sh' } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index d7af018d..d7553221 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,32 +1,33 @@ - + - + - + - + - @@ -34,7 +35,6 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".gui.MainActivity" /> - @@ -47,19 +47,28 @@ + android:theme="@style/DialogWhenLarge" > - + android:theme="@style/DialogWhenLarge" > - + + + + - + + diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/FolderPickerActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/FolderPickerActivity.java new file mode 100644 index 00000000..c40166b6 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/gui/FolderPickerActivity.java @@ -0,0 +1,207 @@ +package com.nutomic.syncthingandroid.gui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import com.nutomic.syncthingandroid.R; +import com.nutomic.syncthingandroid.syncthing.SyncthingService; +import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.Arrays; +import java.util.Stack; + +/** + * Activity that allows selecting a directory in the local file system. + */ +public class FolderPickerActivity extends ActionBarActivity + implements AdapterView.OnItemClickListener, SyncthingService.OnApiChangeListener { + + private static final String TAG = "FolderPickerActivity"; + + public static final String EXTRA_INITIAL_DIRECTORY = "initial_directory"; + + public static final String EXTRA_RESULT_DIRECTORY = "result_directory"; + + private ListView mListView; + + private FileAdapter mAdapter; + + private File mLocation; + + private SyncthingService mSyncthingService; + + private final ServiceConnection mSyncthingServiceConnection = new ServiceConnection() { + + public void onServiceConnected(ComponentName className, IBinder service) { + SyncthingServiceBinder binder = (SyncthingServiceBinder) service; + mSyncthingService = binder.getService(); + mSyncthingService.registerOnApiChangeListener(FolderPickerActivity.this); + } + + public void onServiceDisconnected(ComponentName className) { + mSyncthingService = null; + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.folder_picker_activity); + mListView = (ListView) findViewById(android.R.id.list); + mListView.setOnItemClickListener(this); + mListView.setEmptyView(findViewById(android.R.id.empty)); + mAdapter = new FileAdapter(this); + mListView.setAdapter(mAdapter); + + mLocation = new File(getIntent().getStringExtra(EXTRA_INITIAL_DIRECTORY)); + refresh(); + + bindService(new Intent(this, SyncthingService.class), + mSyncthingServiceConnection, Context.BIND_AUTO_CREATE); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.folder_picker, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.create_folder: + final EditText et = new EditText(this); + AlertDialog dialog = new AlertDialog.Builder(this) + .setTitle(R.string.create_folder) + .setView(et) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + createFolder(et.getText().toString()); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)) + .showSoftInput(et, InputMethodManager.SHOW_IMPLICIT); + } + }); + dialog.show(); + return true; + case R.id.select: + Intent intent = new Intent() + .putExtra(EXTRA_RESULT_DIRECTORY, mLocation.getAbsolutePath()); + setResult(Activity.RESULT_OK, intent); + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Creates a new folder with the given name and enters it. + */ + private void createFolder(String name) { + File newFolder = new File(mLocation, name); + newFolder.mkdir(); + mLocation = newFolder; + refresh(); + } + + /** + * Refreshes the ListView to show the contents of the folder in {@code }mLocation.peek()}. + */ + private void refresh() { + mAdapter.clear(); + File[] contents = mLocation.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + return file.isDirectory(); + } + }); + Arrays.sort(contents); + for (File f : contents) { + mAdapter.add(f); + } + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + mLocation = mAdapter.getItem(i); + refresh(); + } + + private class FileAdapter extends ArrayAdapter { + + public FileAdapter(Context context) { + super(context, android.R.layout.simple_list_item_1); + } + + @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(android.R.layout.simple_list_item_1, parent, false); + } + + TextView title = (TextView) convertView.findViewById(android.R.id.text1); + title.setText(getItem(position).getName()); + return convertView; + } + } + + @Override + public void onBackPressed() { + if (!mLocation.equals(Environment.getExternalStorageDirectory())) { + mLocation = mLocation.getParentFile(); + refresh(); + } + else { + setResult(Activity.RESULT_CANCELED); + finish(); + } + } + + @Override + public void onApiChange(boolean isAvailable) { + if (!isAvailable) { + setResult(Activity.RESULT_CANCELED); + finish(); + } + } + +} diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java index 86597da5..1fec1c32 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/MainActivity.java @@ -16,7 +16,6 @@ import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; import android.support.v4.widget.DrawerLayout; @@ -24,14 +23,10 @@ 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.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; -import android.view.OrientationEventListener; import android.view.View; -import android.widget.FrameLayout; -import android.widget.ProgressBar; import android.widget.TextView; import com.nutomic.syncthingandroid.R; @@ -243,7 +238,7 @@ public class MainActivity extends ActionBarActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.main_menu, menu); + getMenuInflater().inflate(R.menu.main, menu); return true; } diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java index 35a91530..9848dbd2 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/NodeSettingsActivity.java @@ -131,7 +131,7 @@ public class NodeSettingsActivity extends PreferenceActivity implements @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.node_settings_menu, menu); + getMenuInflater().inflate(R.menu.node_settings, menu); return super.onCreateOptionsMenu(menu); } diff --git a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java index 37a4723f..cbb688ab 100644 --- a/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/gui/RepoSettingsActivity.java @@ -1,5 +1,6 @@ package com.nutomic.syncthingandroid.gui; +import android.app.Activity; import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; @@ -7,6 +8,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; +import android.os.Environment; import android.os.IBinder; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; @@ -33,6 +35,8 @@ public class RepoSettingsActivity extends PreferenceActivity implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, SyncthingService.OnApiChangeListener { + private static final int DIRECTORY_REQUEST_CODE = 234; + public static final String ACTION_CREATE = "create"; public static final String ACTION_EDIT = "edit"; @@ -60,7 +64,7 @@ public class RepoSettingsActivity extends PreferenceActivity private EditTextPreference mRepoId; - private EditTextPreference mDirectory; + private Preference mDirectory; private CheckBoxPreference mRepoMaster; @@ -85,8 +89,8 @@ public class RepoSettingsActivity extends PreferenceActivity mRepoId = (EditTextPreference) findPreference("repo_id"); mRepoId.setOnPreferenceChangeListener(this); - mDirectory = (EditTextPreference) findPreference("directory"); - mDirectory.setOnPreferenceChangeListener(this); + mDirectory = findPreference("directory"); + mDirectory.setOnPreferenceClickListener(this); mRepoMaster = (CheckBoxPreference) findPreference("repo_master"); mRepoMaster.setOnPreferenceChangeListener(this); mNodes = (PreferenceScreen) findPreference("nodes"); @@ -131,7 +135,6 @@ public class RepoSettingsActivity extends PreferenceActivity mRepoId.setText(mRepo.ID); mRepoId.setSummary(mRepo.ID); - mDirectory.setText(mRepo.Directory); mDirectory.setSummary(mRepo.Directory); mRepoMaster.setChecked(mRepo.ReadOnly); List nodesList = mSyncthingService.getApi().getNodes(); @@ -162,7 +165,7 @@ public class RepoSettingsActivity extends PreferenceActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.repo_settings_menu, menu); + getMenuInflater().inflate(R.menu.repo_settings, menu); return super.onCreateOptionsMenu(menu); } @@ -262,7 +265,15 @@ public class RepoSettingsActivity extends PreferenceActivity @Override public boolean onPreferenceClick(Preference preference) { - if (preference.equals(mDelete)) { + if (preference.equals(mDirectory)) { + Intent intent = new Intent(this, FolderPickerActivity.class) + .putExtra(FolderPickerActivity.EXTRA_INITIAL_DIRECTORY, + (mRepo.Directory.length() != 0) + ? mRepo.Directory + : Environment.getExternalStorageDirectory().getAbsolutePath()); + startActivityForResult(intent, DIRECTORY_REQUEST_CODE); + } + else if (preference.equals(mDelete)) { new AlertDialog.Builder(this) .setMessage(R.string.delete_repo_confirm) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @@ -279,6 +290,15 @@ public class RepoSettingsActivity extends PreferenceActivity return false; } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && requestCode == DIRECTORY_REQUEST_CODE) { + mRepo.Directory = data.getStringExtra(FolderPickerActivity.EXTRA_RESULT_DIRECTORY); + mDirectory.setSummary(mRepo.Directory); + repoUpdated(); + } + } + private void repoUpdated() { if (getIntent().getAction().equals(ACTION_EDIT)) { mSyncthingService.getApi().editRepo(mRepo, false); diff --git a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java index 8f9467f9..476b739f 100644 --- a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java +++ b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java @@ -96,16 +96,25 @@ public class ConfigXml { changed = true; } - // Set ignorePerms attribute. NodeList repos = mConfig.getDocumentElement().getElementsByTagName("repository"); for (int i = 0; i < repos.getLength(); i++) { Element r = (Element) repos.item(i); + // Set ignorePerms attribute. if (!r.hasAttribute("ignorePerms") || !Boolean.parseBoolean(r.getAttribute("ignorePerms"))) { Log.i(TAG, "Set 'ignorePerms' on repository " + r.getAttribute("id")); r.setAttribute("ignorePerms", Boolean.toString(true)); changed = true; } + + // Replace /sdcard/ in repository path with proper path. + String dir = r.getAttribute("directory"); + if (dir.startsWith("/sdcard")) { + String newDir = dir.replace("/sdcard", + Environment.getExternalStorageDirectory().getAbsolutePath()); + r.setAttribute("directory", newDir); + changed = true; + } } if (changed) { diff --git a/src/main/java/com/nutomic/syncthingandroid/util/ReposAdapter.java b/src/main/java/com/nutomic/syncthingandroid/util/ReposAdapter.java index d580597d..0a444117 100644 --- a/src/main/java/com/nutomic/syncthingandroid/util/ReposAdapter.java +++ b/src/main/java/com/nutomic/syncthingandroid/util/ReposAdapter.java @@ -23,7 +23,7 @@ public class ReposAdapter extends ArrayAdapter private HashMap mModels = new HashMap(); public ReposAdapter(Context context) { - super(context, R.layout.node_list_item); + super(context, R.layout.repo_list_item); } @Override diff --git a/src/main/res/layout/folder_picker_activity.xml b/src/main/res/layout/folder_picker_activity.xml new file mode 100644 index 00000000..83c003de --- /dev/null +++ b/src/main/res/layout/folder_picker_activity.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/src/main/res/menu/folder_picker.xml b/src/main/res/menu/folder_picker.xml new file mode 100644 index 00000000..b6383bc8 --- /dev/null +++ b/src/main/res/menu/folder_picker.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/main_menu.xml b/src/main/res/menu/main.xml similarity index 100% rename from src/main/res/menu/main_menu.xml rename to src/main/res/menu/main.xml diff --git a/src/main/res/menu/node_settings_menu.xml b/src/main/res/menu/node_settings.xml similarity index 100% rename from src/main/res/menu/node_settings_menu.xml rename to src/main/res/menu/node_settings.xml diff --git a/src/main/res/menu/repo_settings_menu.xml b/src/main/res/menu/repo_settings.xml similarity index 100% rename from src/main/res/menu/repo_settings_menu.xml rename to src/main/res/menu/repo_settings.xml diff --git a/src/main/res/values-large/styles.xml b/src/main/res/values-large/styles.xml index 44272522..b7e42215 100644 --- a/src/main/res/values-large/styles.xml +++ b/src/main/res/values-large/styles.xml @@ -1,4 +1,4 @@ -