aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian S <christian@pix-art.de>2016-01-31 16:55:36 +0100
committerChristian S <christian@pix-art.de>2016-01-31 16:55:36 +0100
commit8986a10bc416651828ce6734933a0ed82574f13f (patch)
treecebd8d57eccf8394f9bb1a81162814e5a7b4159d
parent0196442c37d5d172ba75986495977bf16f3095dc (diff)
parent19c2ca520c703576bfbf68626051fe5faab6362f (diff)
Merge branch 'siacs/master'
-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
-rw-r--r--src/main/res/layout/form_boolean.xml13
-rw-r--r--src/main/res/layout/form_text.xml2
-rw-r--r--src/main/res/values/strings.xml4
-rw-r--r--src/main/res/xml/preferences.xml5
20 files changed, 459 insertions, 75 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");
}
diff --git a/src/main/res/layout/form_boolean.xml b/src/main/res/layout/form_boolean.xml
new file mode 100644
index 000000000..fd553acb2
--- /dev/null
+++ b/src/main/res/layout/form_boolean.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="8dp"
+ android:orientation="vertical">
+ <CheckBox
+ android:id="@+id/field"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@color/black87"
+ android:textSize="?attr/TextSizeBody"/>
+</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/layout/form_text.xml b/src/main/res/layout/form_text.xml
index e36ac74e9..31b521e89 100644
--- a/src/main/res/layout/form_text.xml
+++ b/src/main/res/layout/form_text.xml
@@ -2,12 +2,12 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingTop="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index e4fbeff0e..dcf25f50f 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -543,6 +543,9 @@
<string name="pref_xa_on_silent_mode">Not available in silent mode</string>
<string name="update_info">Pix-Art Messenger is checking for an update. If an update is available you will be asked, if you want to update your version. The update process is downloading and installing the new version automatically.</string>
<string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when device is in silent mode</string>
+ <string name="pref_show_connection_options">Extended connection options</string>
+ <string name="pref_show_connection_options_summary">Show hostname and port options when setting up an account</string>
+ <string name="hostname_example">xmpp.example.com</string>
<string name="action_add_account_with_certificate">Add account with certificate</string>
<string name="unable_to_parse_certificate">Unable to parse certificate</string>
<string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string>
@@ -603,4 +606,5 @@
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="no_accounts">(No activated accounts)</string>
+ <string name="this_field_is_required">This field is required</string>
</resources>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index aac580a98..ca6af3f2a 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -175,6 +175,11 @@
android:key="use_tor"
android:summary="@string/pref_use_tor_summary"
android:title="@string/pref_use_tor"/>
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="show_connection_options"
+ android:summary="@string/pref_show_connection_options_summary"
+ android:title="@string/pref_show_connection_options"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_input_options">
<CheckBoxPreference