aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu')
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java13
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java13
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java67
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java8
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java30
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java3
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java3
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java80
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java8
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java56
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java44
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java75
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java72
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java17
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/forms/Data.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/forms/Field.java15
16 files changed, 436 insertions, 74 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index c8253dd49..43a900109 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -79,6 +79,19 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
+ public boolean fetchMapHasErrors(Contact contact) {
+ Jid jid = contact.getJid().toBareJid();
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private static class AxolotlAddressMap<T> {
protected Map<String, Map<Integer, T>> map;
protected final Object MAP_LOCK = new Object();
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index c5831e7ec..f3d891e86 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -743,13 +743,20 @@ public class Message extends AbstractEntity {
}
public boolean isValidInSession() {
- int pastEncryption = this.getPreviousEncryption();
- int futureEncryption = this.getNextEncryption();
+ int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
+ int futureEncryption = getCleanedEncryption(this.getNextEncryption());
boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
|| futureEncryption == ENCRYPTION_NONE
|| pastEncryption != futureEncryption;
- return inUnencryptedSession || this.getEncryption() == pastEncryption;
+ return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
+ }
+
+ private static int getCleanedEncryption(int encryption) {
+ if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
+ return ENCRYPTION_PGP;
+ }
+ return encryption;
}
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 05932acce..4bdf080cf 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -86,7 +86,7 @@ public class FileBackend {
public static String getConversationsImageDirectory() {
return Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ "/Conversations/";
}
@@ -155,12 +155,7 @@ public class FileBackend {
return FileUtils.getPath(mXmppConnectionService,uri);
}
- public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
- Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
- String mime = mXmppConnectionService.getContentResolver().getType(uri);
- String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
- message.setRelativeFilePath(message.getUuid() + "." + extension);
- DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
+ public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
file.getParentFile().mkdirs();
OutputStream os = null;
InputStream is = null;
@@ -183,28 +178,18 @@ public class FileBackend {
close(os);
close(is);
}
- Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
- return file;
+ Log.d(Config.LOGTAG, "output file name " + file.getAbsolutePath());
}
- public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
- throws FileCopyException {
- return this.copyImageToPrivateStorage(message, image, 0);
+ public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
+ String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
+ message.setRelativeFilePath(message.getUuid() + "." + extension);
+ copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
}
- private DownloadableFile copyImageToPrivateStorage(Message message,Uri image, int sampleSize) throws FileCopyException {
- switch(Config.IMAGE_FORMAT) {
- case JPEG:
- message.setRelativeFilePath(message.getUuid()+".jpg");
- break;
- case PNG:
- message.setRelativeFilePath(message.getUuid()+".png");
- break;
- case WEBP:
- message.setRelativeFilePath(message.getUuid()+".webp");
- break;
- }
- DownloadableFile file = getFile(message);
+ private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException {
file.getParentFile().mkdirs();
InputStream is = null;
OutputStream os = null;
@@ -225,7 +210,6 @@ public class FileBackend {
int rotation = getRotation(image);
scaledBitmap = rotate(scaledBitmap, rotation);
boolean targetSizeReached = false;
- long size = 0;
int quality = Config.IMAGE_QUALITY;
while(!targetSizeReached) {
os = new FileOutputStream(file);
@@ -234,14 +218,11 @@ public class FileBackend {
throw new FileCopyException(R.string.error_compressing_image);
}
os.flush();
- size = file.getSize();
- targetSizeReached = size <= Config.IMAGE_MAX_SIZE || quality <= 50;
+ targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50;
quality -= 5;
}
- int width = scaledBitmap.getWidth();
- int height = scaledBitmap.getHeight();
- message.setBody(Long.toString(size) + '|' + width + '|' + height);
- return file;
+ scaledBitmap.recycle();
+ return;
} catch (FileNotFoundException e) {
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
@@ -252,7 +233,7 @@ public class FileBackend {
} catch (OutOfMemoryError e) {
++sampleSize;
if (sampleSize <= 3) {
- return copyImageToPrivateStorage(message, image, sampleSize);
+ copyImageToPrivateStorage(file, image, sampleSize);
} else {
throw new FileCopyException(R.string.error_out_of_memory);
}
@@ -264,6 +245,26 @@ public class FileBackend {
}
}
+ public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException {
+ copyImageToPrivateStorage(file, image, 0);
+ }
+
+ public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException {
+ switch(Config.IMAGE_FORMAT) {
+ case JPEG:
+ message.setRelativeFilePath(message.getUuid()+".jpg");
+ break;
+ case PNG:
+ message.setRelativeFilePath(message.getUuid()+".png");
+ break;
+ case WEBP:
+ message.setRelativeFilePath(message.getUuid()+".webp");
+ break;
+ }
+ copyImageToPrivateStorage(getFile(message), image);
+ updateFileParams(message);
+ }
+
private int getRotation(File file) {
return getRotation(Uri.parse("file://"+file.getAbsolutePath()));
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index a295b2cee..d4fda1b86 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -1969,7 +1969,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- Element form = query.findChild("x","jabber:x:data");
+ Element form = query.findChild("x", "jabber:x:data");
if (form != null) {
conversation.getMucOptions().updateFormData(Data.parse(form));
}
@@ -2378,7 +2378,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
updateConversationUi();
updateRosterUi();
} else {
- Conversation conversation = find(account,avatar.owner.toBareJid());
+ Conversation conversation = find(account, avatar.owner.toBareJid());
if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart());
if (user != null) {
@@ -2586,6 +2586,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false);
}
+ public boolean showExtendedConnectionOptions() {
+ return getPreferences().getBoolean("show_connection_options", false);
+ }
+
public int unreadCount() {
int count = 0;
for (Conversation conversation : getConversations()) {
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 0ea393296..a126b344d 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -5,6 +5,7 @@ import android.app.AlertDialog.Builder;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
@@ -91,7 +92,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private Jid jidToEdit;
private boolean mInitMode = false;
- private boolean mUseTor = false;
+ private boolean mShowOptions = false;
private Account mAccount;
private String messageFingerprint;
@@ -133,7 +134,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
String hostname = null;
int numericPort = 5222;
- if (mUseTor) {
+ if (mShowOptions) {
hostname = mHostname.getText().toString();
final String port = mPort.getText().toString();
if (hostname.contains(" ")) {
@@ -511,8 +512,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
}
}
- this.mUseTor = Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false);
- this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
+ SharedPreferences preferences = getPreferences();
+ boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
+ this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false);
+ mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example);
+ this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
@Override
@@ -598,7 +602,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mHostname.getEditableText().append(this.mAccount.getHostname());
this.mPort.setText("");
this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
- this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
+ this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
if (!mInitMode) {
@@ -740,12 +744,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
} else {
if (this.mAccount.errorStatus()) {
- this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
+ final EditText errorTextField;
+ if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
+ errorTextField = this.mPassword;
+ } else if (mShowOptions
+ && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
+ && this.mHostname.getText().length() > 0) {
+ errorTextField = this.mHostname;
+ } else {
+ errorTextField = this.mAccountJid;
+ }
+ errorTextField.setError(getString(this.mAccount.getStatus().getReadableId()));
if (init || !accountInfoEdited()) {
- this.mAccountJid.requestFocus();
+ errorTextField.requestFocus();
}
} else {
this.mAccountJid.setError(null);
+ this.mPassword.setError(null);
+ this.mHostname.setError(null);
}
this.mStats.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
index 29da0ce6c..eec30798e 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -162,7 +162,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
} else {
if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
keyErrorMessageCard.setVisibility(View.VISIBLE);
- if (lastFetchReport == AxolotlService.FetchStatus.ERROR) {
+ if (lastFetchReport == AxolotlService.FetchStatus.ERROR
+ || contact.getAccount().getAxolotlService().fetchMapHasErrors(contact)) {
keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error);
} else {
keyErrorMessage.setText(R.string.error_no_keys_to_trust);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index 4be4931f7..47414f900 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -78,9 +78,10 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
}
final Jid jid = item.getJid();
if (jid != null) {
+ tvJid.setVisibility(View.VISIBLE);
tvJid.setText(jid.toString());
} else {
- tvJid.setText("");
+ tvJid.setVisibility(View.GONE);
}
tvName.setText(item.getDisplayName());
loadAvatar(item,picture);
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java
new file mode 100644
index 000000000..6cb357a90
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java
@@ -0,0 +1,80 @@
+package eu.siacs.conversations.ui.forms;
+
+import android.content.Context;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.xmpp.forms.Field;
+
+public class FormBooleanFieldWrapper extends FormFieldWrapper {
+
+ protected CheckBox checkBox;
+
+ protected FormBooleanFieldWrapper(Context context, Field field) {
+ super(context, field);
+ checkBox = (CheckBox) view.findViewById(R.id.field);
+ checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ checkBox.setError(null);
+ invokeOnFormFieldValuesEdited();
+ }
+ });
+ }
+
+ @Override
+ protected void setLabel(String label, boolean required) {
+ CheckBox checkBox = (CheckBox) view.findViewById(R.id.field);
+ checkBox.setText(createSpannableLabelString(label, required));
+ }
+
+ @Override
+ public List<String> getValues() {
+ List<String> values = new ArrayList<>();
+ values.add(Boolean.toString(checkBox.isChecked()));
+ return values;
+ }
+
+ @Override
+ protected void setValues(List<String> values) {
+ if (values.size() == 0) {
+ checkBox.setChecked(false);
+ } else {
+ checkBox.setChecked(Boolean.parseBoolean(values.get(0)));
+ }
+ }
+
+ @Override
+ public boolean validates() {
+ if (checkBox.isChecked() || !field.isRequired()) {
+ return true;
+ } else {
+ checkBox.setError(context.getString(R.string.this_field_is_required));
+ checkBox.requestFocus();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean edited() {
+ if (field.getValues().size() == 0) {
+ return checkBox.isChecked();
+ } else {
+ return super.edited();
+ }
+ }
+
+ @Override
+ protected int getLayoutResource() {
+ return R.layout.form_boolean;
+ }
+
+ @Override
+ void setReadOnly(boolean readOnly) {
+ checkBox.setEnabled(!readOnly);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java
index 9e54678a3..ee3064726 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java
@@ -15,10 +15,16 @@ public class FormFieldFactory {
static {
typeTable.put("text-single", FormTextFieldWrapper.class);
typeTable.put("text-multi", FormTextFieldWrapper.class);
+ typeTable.put("text-private", FormTextFieldWrapper.class);
+ typeTable.put("jid-single", FormJidSingleFieldWrapper.class);
+ typeTable.put("boolean", FormBooleanFieldWrapper.class);
}
- public static FormFieldWrapper createFromField(Context context, Field field) {
+ protected static FormFieldWrapper createFromField(Context context, Field field) {
Class clazz = typeTable.get(field.getType());
+ if (clazz == null) {
+ clazz = FormTextFieldWrapper.class;
+ }
return FormFieldWrapper.createFromField(clazz, context, field);
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
index 3af375363..3a21ade3b 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
@@ -1,11 +1,17 @@
package eu.siacs.conversations.ui.forms;
import android.content.Context;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import java.util.List;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Field;
public abstract class FormFieldWrapper {
@@ -13,20 +19,25 @@ public abstract class FormFieldWrapper {
protected final Context context;
protected final Field field;
protected final View view;
+ protected OnFormFieldValuesEdited onFormFieldValuesEditedListener;
protected FormFieldWrapper(Context context, Field field) {
this.context = context;
this.field = field;
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.view = inflater.inflate(getLayoutResource(), null);
- setLabel(field.getLabel(), field.isRequired());
+ String label = field.getLabel();
+ if (label == null) {
+ label = field.getFieldName();
+ }
+ setLabel(label, field.isRequired());
}
- public void submit() {
+ public final void submit() {
this.field.setValues(getValues());
}
- public View getView() {
+ public final View getView() {
return view;
}
@@ -34,14 +45,51 @@ public abstract class FormFieldWrapper {
abstract List<String> getValues();
+ protected abstract void setValues(List<String> values);
+
+ abstract boolean validates();
+
abstract protected int getLayoutResource();
+ abstract void setReadOnly(boolean readOnly);
+
+ protected SpannableString createSpannableLabelString(String label, boolean required) {
+ SpannableString spannableString = new SpannableString(label + (required ? " *" : ""));
+ if (required) {
+ int start = label.length();
+ int end = label.length() + 2;
+ spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0);
+ spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0);
+ }
+ return spannableString;
+ }
+
+ protected void invokeOnFormFieldValuesEdited() {
+ if (this.onFormFieldValuesEditedListener != null) {
+ this.onFormFieldValuesEditedListener.onFormFieldValuesEdited();
+ }
+ }
+
+ public boolean edited() {
+ return !field.getValues().equals(getValues());
+ }
+
+ public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) {
+ this.onFormFieldValuesEditedListener = listener;
+ }
+
protected static <F extends FormFieldWrapper> FormFieldWrapper createFromField(Class<F> c, Context context, Field field) {
try {
- return c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field);
+ F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field);
+ fieldWrapper.setValues(field.getValues());
+ return fieldWrapper;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
+
+ public interface OnFormFieldValuesEdited {
+ void onFormFieldValuesEdited();
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java
new file mode 100644
index 000000000..553e8f21f
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java
@@ -0,0 +1,44 @@
+package eu.siacs.conversations.ui.forms;
+
+import android.content.Context;
+import android.text.InputType;
+
+import java.util.List;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.xmpp.forms.Field;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+public class FormJidSingleFieldWrapper extends FormTextFieldWrapper {
+
+ protected FormJidSingleFieldWrapper(Context context, Field field) {
+ super(context, field);
+ editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+ editText.setHint(R.string.account_settings_example_jabber_id);
+ }
+
+ @Override
+ public boolean validates() {
+ String value = getValue();
+ if (!value.isEmpty()) {
+ try {
+ Jid.fromString(value);
+ } catch (InvalidJidException e) {
+ editText.setError(context.getString(R.string.invalid_jid));
+ editText.requestFocus();
+ return false;
+ }
+ }
+ return super.validates();
+ }
+
+ @Override
+ protected void setValues(List<String> values) {
+ StringBuilder builder = new StringBuilder("");
+ for(String value : values) {
+ builder.append(value);
+ }
+ editText.setText(builder.toString());
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java
index 8f70dd37e..b7dac9510 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java
@@ -1,9 +1,9 @@
package eu.siacs.conversations.ui.forms;
import android.content.Context;
-import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.TextView;
@@ -20,33 +20,78 @@ public class FormTextFieldWrapper extends FormFieldWrapper {
protected FormTextFieldWrapper(Context context, Field field) {
super(context, field);
editText = (EditText) view.findViewById(R.id.field);
- editText.setSingleLine("text-single".equals(field.getType()));
+ editText.setSingleLine(!"text-multi".equals(field.getType()));
+ if ("text-private".equals(field.getType())) {
+ editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ }
+ editText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ editText.setError(null);
+ invokeOnFormFieldValuesEdited();
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
}
@Override
protected void setLabel(String label, boolean required) {
TextView textView = (TextView) view.findViewById(R.id.label);
- SpannableString spannableString = new SpannableString(label + (required ? " *" : ""));
- if (required) {
- int start = label.length();
- int end = label.length() + 2;
- spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0);
- spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0);
- }
- textView.setText(spannableString);
+ textView.setText(createSpannableLabelString(label, required));
+ }
+
+ protected String getValue() {
+ return editText.getText().toString();
}
@Override
- List<String> getValues() {
+ public List<String> getValues() {
List<String> values = new ArrayList<>();
- for (String line : editText.getText().toString().split("\\n")) {
- values.add(line);
+ for (String line : getValue().split("\\n")) {
+ if (line.length() > 0) {
+ values.add(line);
+ }
}
return values;
}
@Override
+ protected void setValues(List<String> values) {
+ StringBuilder builder = new StringBuilder("");
+ for(int i = 0; i < values.size(); ++i) {
+ builder.append(values.get(i));
+ if (i < values.size() - 1 && "text-multi".equals(field.getType())) {
+ builder.append("\n");
+ }
+ }
+ editText.setText(builder.toString());
+ }
+
+ @Override
+ public boolean validates() {
+ if (getValue().trim().length() > 0 || !field.isRequired()) {
+ return true;
+ } else {
+ editText.setError(context.getString(R.string.this_field_is_required));
+ editText.requestFocus();
+ return false;
+ }
+ }
+
+ @Override
protected int getLayoutResource() {
return R.layout.form_text;
}
+
+ @Override
+ void setReadOnly(boolean readOnly) {
+ editText.setEnabled(!readOnly);
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java
new file mode 100644
index 000000000..eafe95cc8
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java
@@ -0,0 +1,72 @@
+package eu.siacs.conversations.ui.forms;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.forms.Field;
+
+public class FormWrapper {
+
+ private final LinearLayout layout;
+
+ private final Data form;
+
+ private final List<FormFieldWrapper> fieldWrappers = new ArrayList<>();
+
+ private FormWrapper(Context context, LinearLayout linearLayout, Data form) {
+ this.form = form;
+ this.layout = linearLayout;
+ this.layout.removeAllViews();
+ for(Field field : form.getFields()) {
+ FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field);
+ if (fieldWrapper != null) {
+ layout.addView(fieldWrapper.getView());
+ fieldWrappers.add(fieldWrapper);
+ }
+ }
+ }
+
+ public Data submit() {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.submit();
+ }
+ this.form.submit();
+ return this.form;
+ }
+
+ public boolean validates() {
+ boolean validates = true;
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ validates &= fieldWrapper.validates();
+ }
+ return validates;
+ }
+
+ public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.setOnFormFieldValuesEditedListener(listener);
+ }
+ }
+
+ public void setReadOnly(boolean b) {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.setReadOnly(b);
+ }
+ }
+
+ public boolean edited() {
+ boolean edited = false;
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ edited |= fieldWrapper.edited();
+ }
+ return edited;
+ }
+
+ public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) {
+ return new FormWrapper(context, layout, form);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index d957a60d8..cc2579a9a 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -243,6 +243,7 @@ public class XmppConnection implements Runnable {
tagWriter = new TagWriter();
this.changeStatus(Account.State.CONNECTING);
final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
+ final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
if (useTor) {
String destination;
if (account.getHostname() == null || account.getHostname().isEmpty()) {
@@ -250,8 +251,16 @@ public class XmppConnection implements Runnable {
} else {
destination = account.getHostname();
}
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR");
- socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort());
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR");
+ socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
+ startXmpp();
+ } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
+ socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
+ }
startXmpp();
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
@@ -540,7 +549,7 @@ public class XmppConnection implements Runnable {
} else if (nextTag.isStart("failed")) {
tagReader.readElement(nextTag);
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed");
- streamId = null;
+ resetStreamId();
if (account.getStatus() != Account.State.ONLINE) {
sendBindRequest();
}
@@ -879,6 +888,7 @@ public class XmppConnection implements Runnable {
public void resetEverything() {
resetStreamId();
clearIqCallbacks();
+ mStanzaQueue.clear();
synchronized (this.disco) {
disco.clear();
}
@@ -1280,7 +1290,6 @@ public class XmppConnection implements Runnable {
}
return;
} else {
- resetStreamId();
if (tagWriter.isActive()) {
tagWriter.finish();
try {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
index d05c9abb3..0053a399c 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java
@@ -53,16 +53,16 @@ public class Data extends Element {
public void submit() {
this.setAttribute("type","submit");
- removeNonFieldChildren();
+ removeUnnecessaryChildren();
for(Field field : getFields()) {
field.removeNonValueChildren();
}
}
- private void removeNonFieldChildren() {
+ private void removeUnnecessaryChildren() {
for(Iterator<Element> iterator = this.children.iterator(); iterator.hasNext();) {
Element element = iterator.next();
- if (!element.getName().equals("field")) {
+ if (!element.getName().equals("field") && !element.getName().equals("title")) {
iterator.remove();
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java
index c1fc808dc..020b34b90 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java
@@ -1,7 +1,9 @@
package eu.siacs.conversations.xmpp.forms;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
import eu.siacs.conversations.xml.Element;
@@ -52,6 +54,19 @@ public class Field extends Element {
return findChildContent("value");
}
+ public List<String> getValues() {
+ List<String> values = new ArrayList<>();
+ for(Element child : getChildren()) {
+ if ("value".equals(child.getName())) {
+ String content = child.getContent();
+ if (content != null) {
+ values.add(content);
+ }
+ }
+ }
+ return values;
+ }
+
public String getLabel() {
return getAttribute("label");
}