1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2025-01-10 20:15:54 +00:00

Improvements for "save to Syncthing" (#939)

* Addressing #899 and #898.
Added Subdirectory browsing
The previously selected folder is remembered.
The subdirectory is remembered for each syncthing folder.
The saved subdirectory in the sharedpreferences is deleted when a folder is deleted.
The root directory of the folderpicker activity is set to the synced folder, so the user can only choose a subfolder within the folder that is being synced.
The folderpicker activity was modified inorder to allow for a custom root directroy to be set.

* Addressing change requests.
Spelling and formatting.

* Addressing change requests.
 - The saved folder subdirectory is now deleted in RestApi.removeFolder(),
   this ensure that the data will be deleted no matter where removeFolder()
   is called from.

 - FolderPickerActivity.createIntentWithRootDir() removed and its functionality moved
   to FolderPickerActivity.createIntent() inorder to simplify the code.

 - getSharedPreference has been replaced with PreferenceManager.getDefaultSharedPreferences.

 - When passing the directory to CopyFileTask getSavedSubdirectory() is now used, instead
   of getting the text from the textview. This is cleaner and ensures that the same method us
   used to get the saved subdirectory everywhere in the ShareActivity.

 - File is used to combine the folder path and subdirectory path instead of strings.
   This ensures that the paths are properly combined. As a result of this CopyFilesTask
   has been modified so it accepts a File instead of a String.

* Addressing change requests
    - Removed the preceding slash from the sub directory and added a trailing slash.
    - TextView now diplays a message when no sub directory is selected.
    - A separate browse button has been added.

* Fixes UI for all screen sizes in the share activity and adds helper method for formatting file
paths.
If there is not space for the save and cancel buttons then the view becomes scrollable
so the buttons can be reached.
This commit is contained in:
Jessie Chatham Spencer 2017-10-23 16:28:26 +02:00 committed by Felix Ableitner
parent 3bec05f718
commit d6ee33e48e
8 changed files with 285 additions and 78 deletions

View file

@ -141,7 +141,7 @@ public class FolderActivity extends SyncthingActivity
mEditIgnores = findViewById(R.id.edit_ignores);
mPathView.setOnClickListener(view ->
startActivityForResult(FolderPickerActivity.createIntent(this, mFolder.path), FolderPickerActivity.DIRECTORY_REQUEST_CODE));
startActivityForResult(FolderPickerActivity.createIntent(this, mFolder.path, null), FolderPickerActivity.DIRECTORY_REQUEST_CODE));
findViewById(R.id.versioningContainer).setOnClickListener(v -> showVersioningDialog());
mEditIgnores.setOnClickListener(v -> editIgnores());

View file

@ -11,9 +11,12 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@ -30,6 +33,9 @@ import com.google.common.collect.Sets;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.util.Util;
import org.w3c.dom.Text;
import java.io.File;
import java.util.ArrayList;
@ -48,6 +54,9 @@ public class FolderPickerActivity extends SyncthingActivity
private static final String EXTRA_INITIAL_DIRECTORY =
"com.nutomic.syncthingandroid.activities.FolderPickerActivity.INITIAL_DIRECTORY";
public static final String EXTRA_ROOT_DIRECTORY =
"com.nutomic.syncthingandroid.activities.FolderPickerActivity.ROOT_DIRECTORY";
public static final String EXTRA_RESULT_DIRECTORY =
"com.nutomic.syncthingandroid.activities.FolderPickerActivity.RESULT_DIRECTORY";
@ -56,18 +65,21 @@ public class FolderPickerActivity extends SyncthingActivity
private ListView mListView;
private FileAdapter mFilesAdapter;
private RootsAdapter mRootsAdapter;
@Inject SharedPreferences mPreferences;
/**
* Location of null means that the list of roots is displayed.
*/
private File mLocation;
public static Intent createIntent(Context context, String currentPath) {
public static Intent createIntent(Context context, String initialDirectory, @Nullable String rootDirectory) {
Intent intent = new Intent(context, FolderPickerActivity.class);
if (!TextUtils.isEmpty(currentPath)) {
intent.putExtra(FolderPickerActivity.EXTRA_INITIAL_DIRECTORY, currentPath);
if (!TextUtils.isEmpty(initialDirectory)) {
intent.putExtra(EXTRA_INITIAL_DIRECTORY, initialDirectory);
}
if (!TextUtils.isEmpty(rootDirectory)) {
intent.putExtra(EXTRA_ROOT_DIRECTORY, rootDirectory);
}
return intent;
@ -101,7 +113,8 @@ public class FolderPickerActivity extends SyncthingActivity
}
/**
* Reads available storage devices/folders from various APIs and inserts them into
* If a root directory is specified it is added to {@link #mRootsAdapter} otherwise
* all available storage devices/folders from various APIs are inserted into
* {@link #mRootsAdapter}.
*/
@SuppressLint("NewApi")
@ -111,22 +124,28 @@ public class FolderPickerActivity extends SyncthingActivity
roots.addAll(Arrays.asList(getExternalFilesDirs(null)));
roots.remove(getExternalFilesDir(null));
}
roots.add(Environment.getExternalStorageDirectory());
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));
}
// Add paths that might not be accessible to Syncthing.
if (mPreferences.getBoolean("advanced_folder_picker", false)) {
Collections.addAll(roots, new File("/storage/").listFiles());
roots.add(new File("/"));
}
String rootDir = getIntent().getStringExtra(EXTRA_ROOT_DIRECTORY);
if (getIntent().hasExtra(EXTRA_ROOT_DIRECTORY) && !TextUtils.isEmpty(rootDir)) {
roots.add(new File(rootDir));
} else {
roots.add(Environment.getExternalStorageDirectory());
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
roots.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS));
}
// Add paths that might not be accessible to Syncthing.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (sp.getBoolean("advanced_folder_picker", false)) {
Collections.addAll(roots, new File("/storage/").listFiles());
roots.add(new File("/"));
}
}
// Remove any invalid directories.
Iterator<File> it = roots.iterator();
while (it.hasNext()) {
@ -135,6 +154,7 @@ public class FolderPickerActivity extends SyncthingActivity
it.remove();
}
}
mRootsAdapter.addAll(Sets.newTreeSet(roots));
}
@ -179,7 +199,7 @@ public class FolderPickerActivity extends SyncthingActivity
return true;
case R.id.select:
Intent intent = new Intent()
.putExtra(EXTRA_RESULT_DIRECTORY, mLocation.getAbsolutePath());
.putExtra(EXTRA_RESULT_DIRECTORY, Util.formatPath(mLocation.getAbsolutePath()));
setResult(Activity.RESULT_OK, intent);
finish();
return true;
@ -291,7 +311,7 @@ public class FolderPickerActivity extends SyncthingActivity
/**
* Goes up a directory, up to the list of roots if there are multiple roots.
*
* <p>
* If we already are in the list of roots, or if we are directly in the only
* root folder, we cancel.
*/

View file

@ -7,10 +7,13 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
@ -37,7 +40,7 @@ import java.util.Map;
/**
* Shares incoming files to syncthing folders.
*
* <p>
* {@link #getDisplayNameForUri} and {@link #getDisplayNameFromContentResolver} are taken from
* ownCloud Android {@see https://github.com/owncloud/android/blob/79664304fdb762b2e04f1ac505f50d0923ddd212/src/com/owncloud/android/utils/UriUtils.java#L193}
*/
@ -45,6 +48,13 @@ public class ShareActivity extends StateDialogActivity
implements SyncthingActivity.OnServiceConnectedListener, SyncthingService.OnApiChangeListener {
private static final String TAG = "ShareActivity";
private static final String PREF_PREVIOUSLY_SELECTED_SYNCTHING_FOLDER = "previously_selected_syncthing_folder";
public static final String PREF_FOLDER_SAVED_SUBDIRECTORY = "saved_sub_directory_";
private TextView mSubDirectoryTextView;
private Spinner mFoldersSpinner;
@Override
public void onApiChange(SyncthingService.State currentState) {
@ -53,12 +63,24 @@ public class ShareActivity extends StateDialogActivity
List<Folder> folders = getApi().getFolders();
// Get the index of the previously selected folder.
int folderIndex = 0;
String savedFolderId = PreferenceManager.getDefaultSharedPreferences(this)
.getString(PREF_PREVIOUSLY_SELECTED_SYNCTHING_FOLDER, "");
for (Folder folder : folders) {
if (folder.id.equals(savedFolderId)) {
folderIndex = folders.indexOf(folder);
break;
}
}
ArrayAdapter<Folder> adapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, folders);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner sItems = findViewById(R.id.folders);
sItems.setAdapter(adapter);
sItems.setSelection(folderIndex);
}
@Override
@ -81,12 +103,15 @@ public class ShareActivity extends StateDialogActivity
registerOnServiceConnectedListener(this);
Spinner mFoldersSpinner = findViewById(R.id.folders);
Button mShareButton = findViewById(R.id.share_button);
Button mCancelButton = findViewById(R.id.cancel_button);
Button browseButton = findViewById(R.id.browse_button);
EditText mShareName = findViewById(R.id.name);
TextView mShareTitle = findViewById(R.id.namesTitle);
mSubDirectoryTextView = findViewById(R.id.sub_directory_Textview);
mFoldersSpinner = findViewById(R.id.folders);
// TODO: add support for EXTRA_TEXT (notes, memos sharing)
ArrayList<Uri> extrasToCopy = new ArrayList<>();
if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
@ -124,9 +149,32 @@ public class ShareActivity extends StateDialogActivity
if (files.size() == 1)
files.entrySet().iterator().next().setValue(mShareName.getText().toString());
Folder folder = (Folder) mFoldersSpinner.getSelectedItem();
new CopyFilesTask(files, folder).execute();
File directory = new File(folder.path, getSavedSubDirectory());
new CopyFilesTask(files, folder, directory).execute();
});
mFoldersSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mSubDirectoryTextView.setText(getSavedSubDirectory());
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
browseButton.setOnClickListener(view -> {
Folder folder = (Folder) mFoldersSpinner.getSelectedItem();
File initialDirectory = new File(folder.path, getSavedSubDirectory());
startActivityForResult(FolderPickerActivity.createIntent(getApplicationContext(),
initialDirectory.getAbsolutePath(), folder.path),
FolderPickerActivity.DIRECTORY_REQUEST_CODE);
});
mCancelButton.setOnClickListener(view -> finish());
mSubDirectoryTextView.setText(getSavedSubDirectory());
}
/**
@ -209,15 +257,32 @@ public class ShareActivity extends StateDialogActivity
return displayName;
}
/**
* Get the previously selected sub directory for the currently selected Syncthing folder.
*/
private String getSavedSubDirectory() {
Folder selectedFolder = (Folder) mFoldersSpinner.getSelectedItem();
String savedSubDirectory = "";
if (selectedFolder != null) {
savedSubDirectory = PreferenceManager.getDefaultSharedPreferences(this)
.getString(PREF_FOLDER_SAVED_SUBDIRECTORY + selectedFolder.id, "");
}
return savedSubDirectory;
}
private class CopyFilesTask extends AsyncTask<Void, Void, Boolean> {
private ProgressDialog mProgress;
private final Map<Uri, String> mFiles;
private final Folder mFolder;
private Map<Uri, String> mFiles;
private Folder mFolder;
private File mDirectory;
private int mCopied = 0, mIgnored = 0;
CopyFilesTask(Map<Uri, String> files, Folder folder) {
CopyFilesTask(Map<Uri, String> files, Folder folder, File directory) {
this.mFiles = files;
this.mFolder = folder;
this.mDirectory = directory;
}
protected void onPreExecute() {
@ -230,7 +295,7 @@ public class ShareActivity extends StateDialogActivity
for (Map.Entry<Uri, String> entry : mFiles.entrySet()) {
InputStream inputStream = null;
try {
File outFile = new File(mFolder.path, entry.getValue());
File outFile = new File(mDirectory, entry.getValue());
if (outFile.isFile()) {
mIgnored++;
continue;
@ -261,10 +326,10 @@ public class ShareActivity extends StateDialogActivity
protected void onPostExecute(Boolean isError) {
Util.dismissDialogSafe(mProgress, ShareActivity.this);
Toast.makeText(ShareActivity.this, mIgnored > 0 ?
getResources().getQuantityString(R.plurals.copy_success_partially, mCopied,
mCopied, mFolder.label, mIgnored) :
getResources().getQuantityString(R.plurals.copy_success, mCopied, mCopied,
mFolder.label),
getResources().getQuantityString(R.plurals.copy_success_partially, mCopied,
mCopied, mFolder.label, mIgnored) :
getResources().getQuantityString(R.plurals.copy_success, mCopied, mCopied,
mFolder.label),
Toast.LENGTH_LONG).show();
if (isError) {
Toast.makeText(ShareActivity.this, getString(R.string.copy_exception),
@ -273,4 +338,32 @@ public class ShareActivity extends StateDialogActivity
finish();
}
}
@Override
protected void onPause() {
super.onPause();
if (mFoldersSpinner.getSelectedItem() != null) {
Folder selectedFolder = (Folder) mFoldersSpinner.getSelectedItem();
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putString(PREF_PREVIOUSLY_SELECTED_SYNCTHING_FOLDER, selectedFolder.id)
.apply();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == FolderPickerActivity.DIRECTORY_REQUEST_CODE && resultCode == RESULT_OK) {
Folder selectedFolder = (Folder) mFoldersSpinner.getSelectedItem();
String folderDirectory = Util.formatPath(selectedFolder.path);
String subDirectory = data.getStringExtra(FolderPickerActivity.EXTRA_RESULT_DIRECTORY);
//Remove the parent directory from the string, so it is only the Sub directory that is displayed to the user.
subDirectory = subDirectory.replace(folderDirectory, "");
mSubDirectoryTextView.setText(subDirectory);
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putString(PREF_FOLDER_SAVED_SUBDIRECTORY + selectedFolder.id, subDirectory)
.apply();
}
}
}

View file

@ -65,7 +65,7 @@ public class StaggeredVersioningFragment extends Fragment {
mPathView.setText(currentPath);
mPathView.setOnClickListener(view ->
startActivityForResult(FolderPickerActivity.createIntent(getContext(), currentPath), FolderPickerActivity.DIRECTORY_REQUEST_CODE));
startActivityForResult(FolderPickerActivity.createIntent(getContext(), currentPath, null), FolderPickerActivity.DIRECTORY_REQUEST_CODE));
}
@Override

View file

@ -3,6 +3,7 @@ package com.nutomic.syncthingandroid.service;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.common.base.Objects;
@ -18,6 +19,7 @@ import com.google.gson.JsonParser;
import com.nutomic.syncthingandroid.BuildConfig;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.activities.RestartActivity;
import com.nutomic.syncthingandroid.activities.ShareActivity;
import com.nutomic.syncthingandroid.http.GetRequest;
import com.nutomic.syncthingandroid.http.PostConfigRequest;
import com.nutomic.syncthingandroid.http.PostScanRequest;
@ -228,6 +230,10 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
public void removeFolder(String id) {
removeFolderInternal(id);
sendConfig();
// Remove saved data from share activity for this folder.
PreferenceManager.getDefaultSharedPreferences(mContext).edit()
.remove(ShareActivity.PREF_FOLDER_SAVED_SUBDIRECTORY+id)
.apply();
}
private void removeFolderInternal(String id) {

View file

@ -15,6 +15,7 @@ import com.nutomic.syncthingandroid.R;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.File;
import java.text.DecimalFormat;
import eu.chainfire.libsuperuser.Shell;
@ -42,7 +43,7 @@ public class Util {
/**
* Converts a number of bytes to a human readable file size (eg 3.5 GiB).
*
* <p>
* Based on http://stackoverflow.com/a/5599842
*/
public static String readableFileSize(Context context, long bytes) {
@ -56,7 +57,7 @@ public class Util {
/**
* Converts a number of bytes to a human readable transfer rate in bytes per second
* (eg 100 KiB/s).
*
* <p>
* Based on http://stackoverflow.com/a/5599842
*/
public static String readableTransferRate(Context context, long bits) {
@ -69,13 +70,14 @@ public class Util {
}
/**
* <<<<<<< HEAD
* Normally an application's data directory is only accessible by the corresponding application.
* Therefore, every file and directory is owned by an application's user and group. When running Syncthing as root,
* it writes to the application's data directory. This leaves files and directories behind which are owned by root having 0600.
* Moreover, those acitons performed as root changes a file's type in terms of SELinux.
* A subsequent start of Syncthing will fail due to insufficient permissions.
* Hence, this method fixes the owner, group and the files' type of the data directory.
*
*
* @return true if the operation was successfully performed. False otherwise.
*/
public static boolean fixAppDataPermissions(Context context) {
@ -136,4 +138,14 @@ public class Util {
dialog.dismiss();
}
/**
* Format a path properly.
*
* @param path String containing the path that needs formatting.
* @return formatted file path as a string.
*/
public static String formatPath(String path) {
return new File(path).toURI().normalize().getPath();
}
}

View file

@ -1,19 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.nutomic.syncthingandroid.activities.ShareActivity"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:fillViewport="true"
android:orientation="vertical"
tools:context="com.nutomic.syncthingandroid.activities.ShareActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/widget_toolbar" />
<RelativeLayout
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
@ -23,69 +26,137 @@
android:paddingTop="8dp">
<TextView
android:id="@+id/namesTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/namesTitle"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="8dp" />
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="text|textMultiLine"
android:layout_below="@+id/namesTitle"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
app:layout_constraintBottom_toTopOf="@+id/folders"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/namesTitle"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:text="@string/folder_title"
android:id="@+id/folder_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/folder_title"
android:layout_below="@+id/name"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="8dp" />
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="7dp"
android:text="@string/folder_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name" />
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/folders"
android:layout_below="@+id/folder_title"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="8dp" />
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/folder_title" />
<Button
android:id="@+id/share_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save_title"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="-8dp"
android:layout_marginTop="8dp"
android:background="?android:selectableItemBackground"
android:minWidth="60dip"
android:text="@string/save_title"
android:textColor="@color/accent"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:textSize="14sp"
android:minWidth="60dip" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/browse_button"
app:layout_constraintVertical_bias="0.965" />
<Button
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel_title"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="?android:selectableItemBackground"
android:textColor="@color/accent"
android:minWidth="50dip"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="@+id/share_button"
android:layout_toStartOf="@+id/share_button"
android:layout_marginRight="14dp"
android:layout_marginEnd="14dp" />
android:text="@string/cancel_title"
android:textColor="@color/accent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.951"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/share_button"
app:layout_constraintTop_toBottomOf="@+id/sub_directory_Textview"
app:layout_constraintVertical_bias="0.964" />
</RelativeLayout>
<TextView
android:id="@+id/sub_folder_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/sub_folder"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/folders" />
<TextView
android:id="@+id/sub_directory_Textview"
style="@style/Widget.Syncthing.TextView.Label.Details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="6dp"
android:drawableLeft="@drawable/ic_folder_black_24dp"
android:drawableStart="@drawable/ic_folder_black_24dp"
android:focusable="true"
android:hint="@string/no_sub_folder_is_selected"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/browse_button"
app:layout_constraintTop_toBottomOf="@+id/sub_folder_title" />
<Button
android:id="@+id/browse_button"
android:layout_width="88dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/browse"
app:layout_constraintBottom_toBottomOf="@+id/sub_directory_Textview"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/sub_directory_Textview" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
</ScrollView>

View file

@ -497,6 +497,9 @@ Please report any problems you encounter via Github.</string>
<item quantity="other">Files List</item>
</plurals>
<!-- Sub Folder title -->
<string name="sub_folder">Sub folder</string>
<!-- SyncthingService -->
@ -620,5 +623,7 @@ Please report any problems you encounter via Github.</string>
<!-- error message if the deviceID/QRCode dialog for some reason cannot be displayed.-->
<string name="could_not_access_deviceid">Could not access device ID.</string>
<string name="browse">Browse</string>
<string name="no_sub_folder_is_selected">No sub folder is selected</string>
</resources>