mirror of
https://github.com/syncthing/syncthing-android.git
synced 2025-01-07 10:42:07 +00:00
Device create/edit dialog: Check address input, fix losing changes on screen rotation (#270)
* Add comments * Add model/Device#checkDeviceAddresses * Add checkDeviceAddresses to device create dialog * FolderActivity: Consolidate createDiscardDialog into showDiscardDialog * folder_settings: R.id.create => R.id.save * device_settings: R.id.create => R.id.save * Edit device dialog: Offer back+discard and save action * Add deviceNeedsToUpdate to savedInstanceState (fixes #271) Remove dependency SyncthingService.OnServiceStateChangeListener * Fix folder settings validation doesn't take place when editing folder (fixes #273) (fixes #265) * Conditional label of the create/save button * Remove workaround for rotation: mVersioning * Fix folder.type is reset from SendOnly to Send&Receive on recreation of FolderActivity while creating a new folder (fixes #274) * Fix typo * Fix typo * Fix typo * Review - Relocate code * Move null checks to the beginning of onSave * Updated de translation * Add ListenAddressesChanged to EventProcessor as unhandled event.
This commit is contained in:
parent
b2864cc3c5
commit
56455fc89d
8 changed files with 327 additions and 243 deletions
|
@ -53,9 +53,7 @@ import static com.nutomic.syncthingandroid.util.Compression.METADATA;
|
|||
/**
|
||||
* Shows device details and allows changing them.
|
||||
*/
|
||||
public class DeviceActivity extends SyncthingActivity
|
||||
implements
|
||||
SyncthingService.OnServiceStateChangeListener {
|
||||
public class DeviceActivity extends SyncthingActivity {
|
||||
|
||||
public static final String EXTRA_NOTIFICATION_ID =
|
||||
"com.github.catfriend1.syncthingandroid.activities.DeviceActivity.NOTIFICATION_ID";
|
||||
|
@ -207,19 +205,47 @@ public class DeviceActivity extends SyncthingActivity
|
|||
mCompressionContainer.setOnClickListener(view -> onCompressionContainerClick());
|
||||
mCustomSyncConditionsDialog.setOnClickListener(view -> onCustomSyncConditionsDialogClick());
|
||||
|
||||
if (savedInstanceState != null){
|
||||
if (mDevice == null) {
|
||||
mDevice = new Gson().fromJson(savedInstanceState.getString("device"), Device.class);
|
||||
}
|
||||
restoreDialogStates(savedInstanceState);
|
||||
}
|
||||
|
||||
findViewById(R.id.editDeviceIdContainer).setVisibility(mIsCreateMode ? View.VISIBLE : View.GONE);
|
||||
mShowDeviceIdContainer.setVisibility(!mIsCreateMode ? View.VISIBLE : View.GONE);
|
||||
if (mIsCreateMode) {
|
||||
if (mDevice == null) {
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
Log.d(TAG, "Retrieving state from savedInstanceState ...");
|
||||
mDevice = new Gson().fromJson(savedInstanceState.getString("device"), Device.class);
|
||||
mDeviceNeedsToUpdate = savedInstanceState.getBoolean("deviceNeedsToUpdate");
|
||||
restoreDialogStates(savedInstanceState);
|
||||
} else {
|
||||
// Fresh init of the edit or create mode.
|
||||
if (mIsCreateMode) {
|
||||
Log.d(TAG, "Initializing create mode ...");
|
||||
initDevice();
|
||||
mDeviceNeedsToUpdate = true;
|
||||
} else {
|
||||
// Edit mode.
|
||||
String passedId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||
Log.d(TAG, "Initializing edit mode: deviceID=" + passedId);
|
||||
RestApi restApi = getApi();
|
||||
List<Device> devices = mConfig.getDevices(restApi, false);
|
||||
mDevice = null;
|
||||
for (Device currentDevice : devices) {
|
||||
if (currentDevice.deviceID.equals(passedId)) {
|
||||
mDevice = currentDevice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mDevice == null) {
|
||||
Log.w(TAG, "Device not found in API update, maybe it was deleted?");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (restApi != null) {
|
||||
restApi.getConnections(this::onReceiveConnections);
|
||||
}
|
||||
mDeviceNeedsToUpdate = false;
|
||||
}
|
||||
}
|
||||
updateViewsAndSetListeners();
|
||||
|
||||
if (mIsCreateMode) {
|
||||
mEditDeviceId.requestFocus();
|
||||
} else {
|
||||
getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
|
@ -236,10 +262,8 @@ public class DeviceActivity extends SyncthingActivity
|
|||
showDeleteDialog();
|
||||
}
|
||||
|
||||
if (mIsCreateMode){
|
||||
if (savedInstanceState.getBoolean(IS_SHOWING_DISCARD_DIALOG)){
|
||||
showDiscardDialog();
|
||||
}
|
||||
if (savedInstanceState.getBoolean(IS_SHOWING_DISCARD_DIALOG)){
|
||||
showDiscardDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,37 +276,11 @@ public class DeviceActivity extends SyncthingActivity
|
|||
SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
|
||||
SyncthingService syncthingService = (SyncthingService) syncthingServiceBinder.getService();
|
||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||
syncthingService.registerOnServiceStateChangeListener(DeviceActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceStateChange(SyncthingService.State currentState) {
|
||||
if (!mIsCreateMode) {
|
||||
RestApi restApi = getApi();
|
||||
List<Device> devices = mConfig.getDevices(restApi, false);
|
||||
String passedId = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||
mDevice = null;
|
||||
for (Device currentDevice : devices) {
|
||||
if (currentDevice.deviceID.equals(passedId)) {
|
||||
mDevice = currentDevice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mDevice == null) {
|
||||
Log.w(TAG, "Device not found in API update, maybe it was deleted?");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (restApi != null) {
|
||||
restApi.getConnections(this::onReceiveConnections);
|
||||
}
|
||||
}
|
||||
updateViewsAndSetListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mIsCreateMode) {
|
||||
if (mDeviceNeedsToUpdate) {
|
||||
showDiscardDialog();
|
||||
}
|
||||
else {
|
||||
|
@ -290,24 +288,12 @@ public class DeviceActivity extends SyncthingActivity
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// We don't want to update every time a TextView's character changes,
|
||||
// so we hold off until the view stops being visible to the user.
|
||||
if (mDeviceNeedsToUpdate) {
|
||||
updateDevice();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
SyncthingService syncthingService = getService();
|
||||
if (syncthingService != null) {
|
||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||
syncthingService.unregisterOnServiceStateChangeListener(DeviceActivity.this);
|
||||
}
|
||||
mEditDeviceId.removeTextChangedListener(mIdTextWatcher);
|
||||
mNameView.removeTextChangedListener(mNameTextWatcher);
|
||||
|
@ -321,10 +307,10 @@ public class DeviceActivity extends SyncthingActivity
|
|||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString("device", new Gson().toJson(mDevice));
|
||||
if (mIsCreateMode){
|
||||
outState.putBoolean(IS_SHOWING_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
||||
Util.dismissDialogSafe(mDiscardDialog, this);
|
||||
}
|
||||
outState.putBoolean("deviceNeedsToUpdate", mDeviceNeedsToUpdate);
|
||||
|
||||
outState.putBoolean(IS_SHOWING_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
||||
Util.dismissDialogSafe(mDiscardDialog, this);
|
||||
|
||||
outState.putBoolean(IS_SHOWING_COMPRESSION_DIALOG, mCompressionDialog != null && mCompressionDialog.isShowing());
|
||||
Util.dismissDialogSafe(mCompressionDialog, this);
|
||||
|
@ -405,7 +391,7 @@ public class DeviceActivity extends SyncthingActivity
|
|||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.create).setVisible(mIsCreateMode);
|
||||
menu.findItem(R.id.save).setTitle(mIsCreateMode ? R.string.create : R.string.save_title);
|
||||
menu.findItem(R.id.share_device_id).setVisible(!mIsCreateMode);
|
||||
menu.findItem(R.id.remove).setVisible(!mIsCreateMode);
|
||||
return true;
|
||||
|
@ -414,24 +400,8 @@ public class DeviceActivity extends SyncthingActivity
|
|||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.create:
|
||||
if (isEmpty(mDevice.deviceID)) {
|
||||
Toast.makeText(this, R.string.device_id_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
if (!mDevice.checkDeviceID()) {
|
||||
Toast.makeText(this, R.string.device_id_invalid, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
if (isEmpty(mDevice.name)) {
|
||||
Toast.makeText(this, R.string.device_name_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
mConfig.addDevice(getApi(), mDevice);
|
||||
finish();
|
||||
case R.id.save:
|
||||
onSave();
|
||||
return true;
|
||||
case R.id.share_device_id:
|
||||
shareDeviceId(this, mDevice.deviceID);
|
||||
|
@ -491,23 +461,51 @@ public class DeviceActivity extends SyncthingActivity
|
|||
mDevice.introducedBy = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the updated device info if in edit mode.
|
||||
* Preconditions: mDeviceNeedsToUpdate == true
|
||||
*/
|
||||
private void updateDevice() {
|
||||
if (mIsCreateMode) {
|
||||
// If we are about to create this device, we cannot update via restApi.
|
||||
private void onSave() {
|
||||
if (mDevice == null) {
|
||||
Log.e(TAG, "onSave: mDevice == null");
|
||||
return;
|
||||
}
|
||||
if (mDevice == null) {
|
||||
Log.e(TAG, "updateDevice: mDevice == null");
|
||||
|
||||
// Validate fields.
|
||||
if (isEmpty(mDevice.deviceID)) {
|
||||
Toast.makeText(this, R.string.device_id_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
if (!mDevice.checkDeviceID()) {
|
||||
Toast.makeText(this, R.string.device_id_invalid, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
if (isEmpty(mDevice.name)) {
|
||||
Toast.makeText(this, R.string.device_name_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
if (!mDevice.checkDeviceAddresses()) {
|
||||
Toast.makeText(this, R.string.device_addresses_invalid, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsCreateMode) {
|
||||
Log.v(TAG, "onSave: Adding device with ID = \'" + mDevice.deviceID + "\'");
|
||||
mConfig.addDevice(getApi(), mDevice);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Edit mode.
|
||||
if (!mDeviceNeedsToUpdate) {
|
||||
// We've got nothing to save.
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
// Log.v(TAG, "deviceID=" + mDevice.deviceID + ", introducedBy=" + mDevice.introducedBy);
|
||||
|
||||
// Save device specific preferences.
|
||||
Log.v(TAG, "updateDevice: mDevice.deviceID = \'" + mDevice.deviceID + "\'");
|
||||
Log.v(TAG, "onSave: Updating device with ID = \'" + mDevice.deviceID + "\'");
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putBoolean(
|
||||
Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_DEVICE + mDevice.deviceID),
|
||||
|
@ -517,8 +515,13 @@ public class DeviceActivity extends SyncthingActivity
|
|||
|
||||
// Update device using RestApi or ConfigXml.
|
||||
mConfig.updateDevice(getApi(), mDevice);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts text line to addresses array.
|
||||
*/
|
||||
private List<String> persistableAddresses(CharSequence userInput) {
|
||||
if (isEmpty(userInput)) {
|
||||
return DYNAMIC_ADDRESS;
|
||||
|
@ -543,6 +546,9 @@ public class DeviceActivity extends SyncthingActivity
|
|||
return Arrays.asList(input.split(", "));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts addresses array to a text line.
|
||||
*/
|
||||
private String displayableAddresses() {
|
||||
if (mDevice.addresses == null) {
|
||||
return "";
|
||||
|
|
|
@ -61,8 +61,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
|||
/**
|
||||
* Shows folder details and allows changing them.
|
||||
*/
|
||||
public class FolderActivity extends SyncthingActivity
|
||||
implements SyncthingService.OnServiceStateChangeListener {
|
||||
public class FolderActivity extends SyncthingActivity {
|
||||
|
||||
public static final String EXTRA_NOTIFICATION_ID =
|
||||
"com.github.catfriend1.syncthingandroid.activities.FolderActivity.NOTIFICATION_ID";
|
||||
|
@ -125,8 +124,6 @@ public class FolderActivity extends SyncthingActivity
|
|||
private Dialog mDeleteDialog;
|
||||
private Dialog mDiscardDialog;
|
||||
|
||||
private Folder.Versioning mVersioning;
|
||||
|
||||
private final TextWatcher mTextWatcher = new TextWatcherAdapter() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
|
@ -221,34 +218,72 @@ public class FolderActivity extends SyncthingActivity
|
|||
findViewById(R.id.pullOrderContainer).setOnClickListener(v -> showPullOrderDialog());
|
||||
findViewById(R.id.versioningContainer).setOnClickListener(v -> showVersioningDialog());
|
||||
|
||||
if (mIsCreateMode) {
|
||||
if (savedInstanceState != null) {
|
||||
mFolder = new Gson().fromJson(savedInstanceState.getString("mFolder"), Folder.class);
|
||||
mFolderUri = savedInstanceState.getParcelable("mFolderUri");
|
||||
}
|
||||
if (mFolder == null) {
|
||||
if (savedInstanceState != null) {
|
||||
Log.d(TAG, "Retrieving state from savedInstanceState ...");
|
||||
mFolder = new Gson().fromJson(savedInstanceState.getString("mFolder"), Folder.class);
|
||||
mFolderNeedsToUpdate = savedInstanceState.getBoolean("mFolderNeedsToUpdate");
|
||||
mIgnoreListNeedsToUpdate = savedInstanceState.getBoolean("mIgnoreListNeedsToUpdate");
|
||||
mFolderUri = savedInstanceState.getParcelable("mFolderUri");
|
||||
restoreDialogStates(savedInstanceState);
|
||||
} else {
|
||||
// Fresh init of the edit or create mode.
|
||||
if (mIsCreateMode) {
|
||||
Log.d(TAG, "Initializing create mode ...");
|
||||
initFolder();
|
||||
mFolderNeedsToUpdate = true;
|
||||
} else {
|
||||
// Edit mode.
|
||||
String passedId = getIntent().getStringExtra(EXTRA_FOLDER_ID);
|
||||
Log.d(TAG, "Initializing edit mode: folder.id=" + passedId);
|
||||
RestApi restApi = getApi();
|
||||
List<Folder> folders = mConfig.getFolders(restApi);
|
||||
mFolder = null;
|
||||
for (Folder currentFolder : folders) {
|
||||
if (currentFolder.id.equals(passedId)) {
|
||||
mFolder = currentFolder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mFolder == null) {
|
||||
Log.w(TAG, "Folder not found in API update, maybe it was deleted?");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mConfig.getFolderIgnoreList(restApi, mFolder, this::onReceiveFolderIgnoreList);
|
||||
mFolderNeedsToUpdate = false;
|
||||
}
|
||||
|
||||
// If the extra is set, we should automatically share the current folder with the given device.
|
||||
if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
|
||||
Device device = new Device();
|
||||
device.deviceID = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||
mFolder.addDevice(device);
|
||||
mFolderNeedsToUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsCreateMode) {
|
||||
mEditIgnoreListTitle.setEnabled(false);
|
||||
mEditIgnoreListContent.setEnabled(false);
|
||||
}
|
||||
else {
|
||||
// Prepare edit mode.
|
||||
} else {
|
||||
// Edit mode.
|
||||
mIdView.setFocusable(false);
|
||||
mIdView.setEnabled(false);
|
||||
mPathView.setFocusable(false);
|
||||
mPathView.setEnabled(false);
|
||||
}
|
||||
checkWriteAndUpdateUI();
|
||||
updateViewsAndSetListeners();
|
||||
|
||||
// Open keyboard on label view in edit mode.
|
||||
mLabelView.requestFocus();
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.getBoolean(IS_SHOWING_DELETE_DIALOG)) {
|
||||
showDeleteDialog();
|
||||
} else if (savedInstanceState.getBoolean(IS_SHOW_DISCARD_DIALOG)) {
|
||||
showDiscardDialog();
|
||||
}
|
||||
private void restoreDialogStates(Bundle savedInstanceState) {
|
||||
if (savedInstanceState.getBoolean(IS_SHOWING_DELETE_DIALOG)) {
|
||||
showDeleteDialog();
|
||||
} else if (savedInstanceState.getBoolean(IS_SHOW_DISCARD_DIALOG)) {
|
||||
showDiscardDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +380,7 @@ public class FolderActivity extends SyncthingActivity
|
|||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mIsCreateMode) {
|
||||
if (mFolderNeedsToUpdate) {
|
||||
showDiscardDialog();
|
||||
}
|
||||
else {
|
||||
|
@ -353,25 +388,12 @@ public class FolderActivity extends SyncthingActivity
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// We don't want to update every time a TextView's character changes,
|
||||
// so we hold off until the view stops being visible to the user.
|
||||
if (mFolderNeedsToUpdate) {
|
||||
Log.v(TAG, "onPause: mFolderNeedsToUpdate=true, mIgnoreListNeedsToUpdate=" + Boolean.toString(mIgnoreListNeedsToUpdate));
|
||||
updateFolder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
SyncthingService syncthingService = getService();
|
||||
if (syncthingService != null) {
|
||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||
syncthingService.unregisterOnServiceStateChangeListener(FolderActivity.this);
|
||||
}
|
||||
mLabelView.removeTextChangedListener(mTextWatcher);
|
||||
mIdView.removeTextChangedListener(mTextWatcher);
|
||||
|
@ -381,17 +403,16 @@ public class FolderActivity extends SyncthingActivity
|
|||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString("mFolder", new Gson().toJson(mFolder));
|
||||
outState.putBoolean("mFolderNeedsToUpdate", mFolderNeedsToUpdate);
|
||||
outState.putBoolean("mIgnoreListNeedsToUpdate", mIgnoreListNeedsToUpdate);
|
||||
outState.putParcelable("mFolderUri", mFolderUri);
|
||||
|
||||
outState.putBoolean(IS_SHOWING_DELETE_DIALOG, mDeleteDialog != null && mDeleteDialog.isShowing());
|
||||
Util.dismissDialogSafe(mDeleteDialog, this);
|
||||
|
||||
outState.putBoolean(IS_SHOW_DISCARD_DIALOG, mDiscardDialog != null && mDiscardDialog.isShowing());
|
||||
Util.dismissDialogSafe(mDiscardDialog, this);
|
||||
|
||||
if (mIsCreateMode) {
|
||||
outState.putString("mFolder", new Gson().toJson(mFolder));
|
||||
outState.putParcelable("mFolderUri", mFolderUri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -403,47 +424,6 @@ public class FolderActivity extends SyncthingActivity
|
|||
SyncthingServiceBinder syncthingServiceBinder = (SyncthingServiceBinder) iBinder;
|
||||
SyncthingService syncthingService = (SyncthingService) syncthingServiceBinder.getService();
|
||||
syncthingService.getNotificationHandler().cancelConsentNotification(getIntent().getIntExtra(EXTRA_NOTIFICATION_ID, 0));
|
||||
syncthingService.registerOnServiceStateChangeListener(FolderActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceStateChange(SyncthingService.State currentState) {
|
||||
if (mFolderNeedsToUpdate) {
|
||||
Log.d(TAG, "onServiceStateChange: Suppressing reload of folder config as changes were made to that folder in the meantime.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mIsCreateMode) {
|
||||
Log.d(TAG, "onServiceStateChange: (Re)loading folder config ...");
|
||||
RestApi restApi = getApi();
|
||||
List<Folder> folders = mConfig.getFolders(restApi);
|
||||
String passedId = getIntent().getStringExtra(EXTRA_FOLDER_ID);
|
||||
mFolder = null;
|
||||
for (Folder currentFolder : folders) {
|
||||
if (currentFolder.id.equals(passedId)) {
|
||||
mFolder = currentFolder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mFolder == null) {
|
||||
Log.w(TAG, "Folder not found in API update, maybe it was deleted?");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mConfig.getFolderIgnoreList(restApi, mFolder, this::onReceiveFolderIgnoreList);
|
||||
}
|
||||
|
||||
// If the extra is set, we should automatically share the current folder with the given device.
|
||||
if (getIntent().hasExtra(EXTRA_DEVICE_ID)) {
|
||||
Device device = new Device();
|
||||
device.deviceID = getIntent().getStringExtra(EXTRA_DEVICE_ID);
|
||||
mFolder.addDevice(device);
|
||||
mFolderNeedsToUpdate = true;
|
||||
}
|
||||
|
||||
checkWriteAndUpdateUI();
|
||||
attemptToApplyVersioningConfig();
|
||||
updateViewsAndSetListeners();
|
||||
}
|
||||
|
||||
private void onReceiveFolderIgnoreList(FolderIgnoreList folderIgnoreList) {
|
||||
|
@ -456,16 +436,6 @@ public class FolderActivity extends SyncthingActivity
|
|||
mEditIgnoreListContent.addTextChangedListener(mIgnoreListContentTextWatcher);
|
||||
}
|
||||
|
||||
// If the FolderActivity gets recreated after the VersioningDialogActivity is closed, then the result from the VersioningDialogActivity will be received before
|
||||
// the mFolder variable has been recreated, so the versioning config will be stored in the mVersioning variable until the mFolder variable has been
|
||||
// recreated in the onServiceStateChange(). This has been observed to happen after the screen orientation has changed while the VersioningDialogActivity was open.
|
||||
private void attemptToApplyVersioningConfig() {
|
||||
if (mFolder != null && mVersioning != null){
|
||||
mFolder.versioning = mVersioning;
|
||||
mVersioning = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViewsAndSetListeners() {
|
||||
mLabelView.removeTextChangedListener(mTextWatcher);
|
||||
mIdView.removeTextChangedListener(mTextWatcher);
|
||||
|
@ -528,7 +498,7 @@ public class FolderActivity extends SyncthingActivity
|
|||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.findItem(R.id.create).setVisible(mIsCreateMode);
|
||||
menu.findItem(R.id.save).setTitle(mIsCreateMode ? R.string.create : R.string.save_title);
|
||||
menu.findItem(R.id.remove).setVisible(!mIsCreateMode);
|
||||
return true;
|
||||
}
|
||||
|
@ -536,39 +506,8 @@ public class FolderActivity extends SyncthingActivity
|
|||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.create:
|
||||
if (TextUtils.isEmpty(mFolder.id)) {
|
||||
Toast.makeText(this, R.string.folder_id_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
if (TextUtils.isEmpty(mFolder.label)) {
|
||||
Toast.makeText(this, R.string.folder_label_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
if (TextUtils.isEmpty(mFolder.path)) {
|
||||
Toast.makeText(this, R.string.folder_path_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
mFolderUri != null &&
|
||||
mFolder.type.equals(Constants.FOLDER_TYPE_SEND_ONLY)) {
|
||||
/**
|
||||
* Normally, syncthing takes care of creating the ".stfolder" marker.
|
||||
* This fails on newer android versions if the syncthing binary only has
|
||||
* readonly access on the path and the user tries to configure a
|
||||
* sendonly folder. To fix this, we'll precreate the marker using java code.
|
||||
*/
|
||||
DocumentFile dfFolder = DocumentFile.fromTreeUri(this, mFolderUri);
|
||||
if (dfFolder != null) {
|
||||
Log.v(TAG, "Creating new directory " + mFolder.path + File.separator + FOLDER_MARKER_NAME);
|
||||
dfFolder.createDirectory(FOLDER_MARKER_NAME);
|
||||
}
|
||||
}
|
||||
mConfig.addFolder(getApi(), mFolder);
|
||||
finish();
|
||||
case R.id.save:
|
||||
onSave();
|
||||
return true;
|
||||
case R.id.remove:
|
||||
showDeleteDialog();
|
||||
|
@ -662,8 +601,12 @@ public class FolderActivity extends SyncthingActivity
|
|||
* because the user most probably intentionally chose a special folder like
|
||||
* "[storage]/Android/data/com.nutomic.syncthingandroid/files"
|
||||
* or enabled root mode thus having write access.
|
||||
* Default from {@link #initFolder} was already set in {@link #onCreate}.
|
||||
* mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE;
|
||||
* We won't set it again here as this would cause user selection to be reset on
|
||||
* screen rotation - as we don't know if we restored the activity or created
|
||||
* a fresh one.
|
||||
*/
|
||||
mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE;
|
||||
updateFolderTypeDescription();
|
||||
} else {
|
||||
mEditIgnoreListTitle.setEnabled(true);
|
||||
|
@ -694,6 +637,9 @@ public class FolderActivity extends SyncthingActivity
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init a new folder in mIsCreateMode, used in {@link #onCreate}.
|
||||
*/
|
||||
private void initFolder() {
|
||||
mFolder = new Folder();
|
||||
mFolder.id = (getIntent().hasExtra(EXTRA_FOLDER_ID))
|
||||
|
@ -708,7 +654,7 @@ public class FolderActivity extends SyncthingActivity
|
|||
*/
|
||||
mFolder.rescanIntervalS = 3600;
|
||||
mFolder.paused = false;
|
||||
mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE;
|
||||
mFolder.type = Constants.FOLDER_TYPE_SEND_RECEIVE; // Default for {@link #checkWriteAndUpdateUI}.
|
||||
mFolder.versioning = new Folder.Versioning();
|
||||
}
|
||||
|
||||
|
@ -735,24 +681,60 @@ public class FolderActivity extends SyncthingActivity
|
|||
deviceView.setOnCheckedChangeListener(mCheckedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the updated folder info if in edit mode.
|
||||
* Preconditions:
|
||||
* mFolderNeedsToUpdate == true
|
||||
* mIgnoreListNeedsToUpdate == true (Optional)
|
||||
*/
|
||||
private void updateFolder() {
|
||||
if (mIsCreateMode) {
|
||||
// If we are about to create this folder, we cannot update via restApi.
|
||||
private void onSave() {
|
||||
if (mFolder == null) {
|
||||
Log.e(TAG, "onSave: mFolder == null");
|
||||
return;
|
||||
}
|
||||
if (mFolder == null) {
|
||||
Log.e(TAG, "updateFolder: mFolder == null");
|
||||
|
||||
// Validate fields.
|
||||
if (TextUtils.isEmpty(mFolder.id)) {
|
||||
Toast.makeText(this, R.string.folder_id_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
if (TextUtils.isEmpty(mFolder.label)) {
|
||||
Toast.makeText(this, R.string.folder_label_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
if (TextUtils.isEmpty(mFolder.path)) {
|
||||
Toast.makeText(this, R.string.folder_path_required, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsCreateMode) {
|
||||
Log.v(TAG, "onSave: Adding folder with ID = \'" + mFolder.id + "\'");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
mFolderUri != null &&
|
||||
mFolder.type.equals(Constants.FOLDER_TYPE_SEND_ONLY)) {
|
||||
/**
|
||||
* Normally, syncthing takes care of creating the ".stfolder" marker.
|
||||
* This fails on newer android versions if the syncthing binary only has
|
||||
* readonly access on the path and the user tries to configure a
|
||||
* sendonly folder. To fix this, we'll precreate the marker using java code.
|
||||
*/
|
||||
DocumentFile dfFolder = DocumentFile.fromTreeUri(this, mFolderUri);
|
||||
if (dfFolder != null) {
|
||||
Log.v(TAG, "onSave: Creating new directory " + mFolder.path + File.separator + FOLDER_MARKER_NAME);
|
||||
dfFolder.createDirectory(FOLDER_MARKER_NAME);
|
||||
}
|
||||
}
|
||||
mConfig.addFolder(getApi(), mFolder);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Edit mode.
|
||||
if (!mFolderNeedsToUpdate) {
|
||||
// We've got nothing to save.
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Save folder specific preferences.
|
||||
Log.v(TAG, "updateFolder: mFolder.id = \'" + mFolder.id + "\'");
|
||||
Log.v(TAG, "onSave: Updating folder with ID = \'" + mFolder.id + "\'");
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putBoolean(
|
||||
Constants.DYN_PREF_OBJECT_CUSTOM_SYNC_CONDITIONS(Constants.PREF_OBJECT_PREFIX_FOLDER + mFolder.id),
|
||||
|
@ -770,41 +752,39 @@ public class FolderActivity extends SyncthingActivity
|
|||
|
||||
// Update folder using RestApi or ConfigXml.
|
||||
mConfig.updateFolder(restApi, mFolder);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
private void showDiscardDialog(){
|
||||
mDiscardDialog = createDiscardDialog();
|
||||
mDiscardDialog.show();
|
||||
}
|
||||
|
||||
private Dialog createDiscardDialog() {
|
||||
return new AlertDialog.Builder(this)
|
||||
mDiscardDialog = new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_discard_changes)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> finish())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create();
|
||||
mDiscardDialog.show();
|
||||
}
|
||||
|
||||
private void updateVersioning(Bundle arguments) {
|
||||
if (mFolder != null){
|
||||
mVersioning = mFolder.versioning;
|
||||
} else {
|
||||
mVersioning = new Folder.Versioning();
|
||||
if (mFolder == null) {
|
||||
Log.e(TAG, "updateVersioning: mFolder == null");
|
||||
return;
|
||||
}
|
||||
if (mFolder.versioning == null) {
|
||||
mFolder.versioning = new Folder.Versioning();
|
||||
}
|
||||
|
||||
String type = arguments.getString("type");
|
||||
arguments.remove("type");
|
||||
|
||||
if (type.equals("none")){
|
||||
mVersioning = new Folder.Versioning();
|
||||
if (type.equals("none")) {
|
||||
mFolder.versioning = new Folder.Versioning();
|
||||
} else {
|
||||
for (String key : arguments.keySet()) {
|
||||
mVersioning.params.put(key, arguments.getString(key));
|
||||
mFolder.versioning.params.put(key, arguments.getString(key));
|
||||
}
|
||||
mVersioning.type = type;
|
||||
mFolder.versioning.type = type;
|
||||
}
|
||||
|
||||
attemptToApplyVersioningConfig();
|
||||
updateVersioningDescription();
|
||||
mFolderNeedsToUpdate = true;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ public class Device {
|
|||
public List<PendingFolder> pendingFolders;
|
||||
public List<IgnoredFolder> ignoredFolders;
|
||||
|
||||
// private static final String TAG = "Device";
|
||||
|
||||
/**
|
||||
* Relevant fields for Folder.List<Device> "shared-with-device" model,
|
||||
* handled by {@link ConfigRouter#updateFolder and ConfigXml#updateFolder}
|
||||
|
@ -112,4 +114,93 @@ public class Device {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if device.addresses elements are correctly formatted.
|
||||
* See https://docs.syncthing.net/users/config.html#device-element for what is correct.
|
||||
* It can be improved in the future because it doesn't catch all mistakes a user can do.
|
||||
* It catches the most common mistakes.
|
||||
*/
|
||||
public Boolean checkDeviceAddresses() {
|
||||
if (this.addresses == null) {
|
||||
return false;
|
||||
}
|
||||
for (String address : this.addresses) {
|
||||
// Log.v(TAG, "address=(" + address + ")");
|
||||
if (address.equals("dynamic")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* RegEx documentation:
|
||||
*
|
||||
* - Matching
|
||||
* tcp://127.0.0.1:4000
|
||||
* tcp4://127.0.0.1:4000
|
||||
* tcp6://127.0.0.1:4000
|
||||
* tcp4://127.0.0.1
|
||||
* tcp://[2001:db8::23:42]
|
||||
* tcp://[2001:db8::23:42]:12345
|
||||
* tcp://myserver
|
||||
* tcp://myserver:12345
|
||||
*
|
||||
* - Not-Matching
|
||||
* tcp8://127.0.0.1
|
||||
* udp4://127.0.0.1
|
||||
*/
|
||||
if (!address.matches("^tcp([46])?://.*$")) {
|
||||
// Log.v(TAG, "Invalid protocol.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Separate protocol from address and port.
|
||||
String[] addressSplit = address.split("://");
|
||||
if (addressSplit.length == 1) {
|
||||
// There's only the protocol given, nothing more.
|
||||
// Log.v(TAG, "There's only the protocol given, nothing more.");
|
||||
return false;
|
||||
}
|
||||
else if (addressSplit.length == 2) {
|
||||
// Check if the address ends with ":" or "]:"
|
||||
if (addressSplit[addressSplit.length-1].endsWith(":") ||
|
||||
addressSplit[addressSplit.length-1].endsWith("]:")) {
|
||||
// The address ends with ":". Will match "tcp://myserver:"
|
||||
// Log.v(TAG, "address ends with \":\" or \"]:\". Will match \"tcp://myserver:\".");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there's a "hostname:port" number given in the part after "://".
|
||||
String[] hostnamePortSplit = addressSplit[addressSplit.length-1].split(":");
|
||||
if (hostnamePortSplit.length > 1) {
|
||||
// Check if the hostname or IP address given before the port is empty.
|
||||
if (TextUtils.isEmpty(hostnamePortSplit[0])) {
|
||||
// Empty hostname or IP address before the port. Will match "tcp://:4000"
|
||||
// Log.v(TAG, "Empty hostname or IP address before the port.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there's a port number given in the last part.
|
||||
String potentialPort = hostnamePortSplit[hostnamePortSplit.length-1];
|
||||
if (!potentialPort.endsWith("]")) {
|
||||
// It's not the end of an IPv6 address and likely a port number.
|
||||
// Log.v(TAG, "... potentialPort=(" + potentialPort + ")");
|
||||
Integer port = 0;
|
||||
try {
|
||||
port = Integer.parseInt(potentialPort);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (port < 1 || port > 65535) {
|
||||
// Invalid port number.
|
||||
// Log.v(TAG, "Invalid port number.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Protocol is given more than one time. Will match "tcp://tcp://"
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,6 +162,7 @@ public class EventProcessor implements Runnable, RestApi.OnReceiveEventListener
|
|||
case "FolderSummary":
|
||||
case "FolderWatchStateChanged":
|
||||
case "ItemStarted":
|
||||
case "ListenAddressesChanged":
|
||||
case "LocalIndexUpdated":
|
||||
case "LoginAttempt":
|
||||
case "RemoteDownloadProgress":
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/create"
|
||||
android:id="@+id/save"
|
||||
android:title="@string/add"
|
||||
android:icon="@drawable/ic_done_white_24dp"
|
||||
app:showAsAction="always|withText" />
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/create"
|
||||
android:id="@+id/save"
|
||||
android:icon="@drawable/ic_done_white_24dp"
|
||||
android:title="@string/create"
|
||||
app:showAsAction="always|withText" />
|
||||
|
|
|
@ -305,6 +305,9 @@ Bitte melden Sie auftretende Probleme via GitHub.</string>
|
|||
<!-- Toast shown when trying to create a device with an empty name -->
|
||||
<string name="device_name_required">Der Gerätename darf nicht leer sein</string>
|
||||
|
||||
<!-- Toast shown when trying to create a device with invalid addresses -->
|
||||
<string name="device_addresses_invalid">Die Geräteadressen sind nicht im richtigen Format. Bitte versuche es erneut. Sieh Dir die Syncthing Doku für weitere Hilfe an.</string>
|
||||
|
||||
<!-- Content description for device ID qr code icon -->
|
||||
<string name="scan_qr_code_description">QR Code scannen</string>
|
||||
|
||||
|
|
|
@ -308,6 +308,9 @@ Please report any problems you encounter via Github.</string>
|
|||
<!-- Toast shown when trying to create a device with an empty name -->
|
||||
<string name="device_name_required">The device name must not be empty</string>
|
||||
|
||||
<!-- Toast shown when trying to create a device with invalid addresses -->
|
||||
<string name="device_addresses_invalid">The device addresses are not formatted correctly. Please retry. Look at the Syncthing docs for more info.</string>
|
||||
|
||||
<!-- Content description for device ID qr code icon -->
|
||||
<string name="scan_qr_code_description">Scan QR Code</string>
|
||||
|
||||
|
|
Loading…
Reference in a new issue