- */
- private void parseStartDocument() {
- mListener.startDocument();
- mParserOffset += (2 * WORD_SIZE);
- }
-
- /**
- * the string table starts with the following 4bytes words :
- *
- *
0th word : 0x1c0001
- *
1st word : chunk size
- *
2nd word : number of string in the string table
- *
3rd word : number of styles in the string table
- *
4th word : flags - sorted/utf8 flag (0)
- *
5th word : Offset to String data
- *
6th word : Offset to style data
- *
- */
- private void parseStringTable() {
-
- int chunk = getLEWord(mParserOffset + (1 * WORD_SIZE));
- mStringsCount = getLEWord(mParserOffset + (2 * WORD_SIZE));
- mStylesCount = getLEWord(mParserOffset + (3 * WORD_SIZE));
- int strOffset = mParserOffset
- + getLEWord(mParserOffset + (5 * WORD_SIZE));
- int styleOffset = getLEWord(mParserOffset + (6 * WORD_SIZE));
-
- mStringsTable = new String[mStringsCount];
- int offset;
- for (int i = 0; i < mStringsCount; ++i) {
- offset = strOffset
- + getLEWord(mParserOffset + ((i + 7) * WORD_SIZE));
- mStringsTable[i] = getStringFromStringTable(offset);
- }
-
- if (styleOffset > 0) {
-// Log.w(TAG, "Unread styles");
- for (int i = 0; i < mStylesCount; ++i) {
- // TODO read the styles ???
- }
- }
-
- mParserOffset += chunk;
- }
-
- /**
- * the resource ids table starts with the following 4bytes words :
- *
- *
0th word : 0x00080180
- *
1st word : chunk size
- *
- */
- private void parseResourceTable() {
- int chunk = getLEWord(mParserOffset + (1 * WORD_SIZE));
- mResCount = (chunk / 4) - 2;
-
- mResourcesIds = new int[mResCount];
- for (int i = 0; i < mResCount; ++i) {
- mResourcesIds[i] = getLEWord(mParserOffset + ((i + 2) * WORD_SIZE));
- }
-
- mParserOffset += chunk;
- }
-
- /**
- * A namespace tag contains the following 4bytes words :
- *
- *
0th word : 0x00100100 = Start NS / 0x00100101 = end NS
- *
1st word : chunk size
- *
2nd word : line this tag appeared
- *
3rd word : optional xml comment for element (usually 0xFFFFFF)
- *
4th word : index of namespace prefix in StringIndexTable
- *
5th word : index of namespace uri in StringIndexTable
- *
- */
- private void parseNamespace(boolean start) {
- final int prefixIdx = getLEWord(mParserOffset + (4 * WORD_SIZE));
- final int uriIdx = getLEWord(mParserOffset + (5 * WORD_SIZE));
-
- final String uri = getString(uriIdx);
- final String prefix = getString(prefixIdx);
-
- if (start) {
- mListener.startPrefixMapping(prefix, uri);
- mNamespaces.put(uri, prefix);
- } else {
- mListener.endPrefixMapping(prefix, uri);
- mNamespaces.remove(uri);
- }
-
- // Offset to first tag
- mParserOffset += (6 * WORD_SIZE);
- }
-
- /**
- * A start tag will start with the following 4bytes words :
- *
- *
0th word : 0x00100102 = Start_Tag
- *
1st word : chunk size
- *
2nd word : line this tag appeared in the original file
- *
3rd word : optional xml comment for element (usually 0xFFFFFF)
- *
4th word : index of namespace uri in StringIndexTable, or 0xFFFFFFFF
- * for default NS
- *
5th word : index of element name in StringIndexTable
- *
6th word : size of attribute structures to follow
- *
7th word : number of attributes following the start tag
- *
8th word : index of id attribute (0 if none)
- *
- */
- private void parseStartTag() {
- // get tag info
- final int uriIdx = getLEWord(mParserOffset + (4 * WORD_SIZE));
- final int nameIdx = getLEWord(mParserOffset + (5 * WORD_SIZE));
- final int attrCount = getLEShort(mParserOffset + (7 * WORD_SIZE));
-
- final String name = getString(nameIdx);
- String uri, qname;
- if (uriIdx == 0xFFFFFFFF) {
- uri = "";
- qname = name;
- } else {
- uri = getString(uriIdx);
- if (mNamespaces.containsKey(uri)) {
- qname = mNamespaces.get(uri) + ':' + name;
- } else {
- qname = name;
- }
- }
-
- // offset to start of attributes
- mParserOffset += (9 * WORD_SIZE);
-
- final Attribute[] attrs = new Attribute[attrCount]; // NOPMD
- for (int a = 0; a < attrCount; a++) {
- attrs[a] = parseAttribute(); // NOPMD
-
- // offset to next attribute or tag
- mParserOffset += (5 * 4);
- }
-
- mListener.startElement(uri, name, qname, attrs);
- }
-
- /**
- * An attribute will have the following 4bytes words :
- *
- *
0th word : index of namespace uri in StringIndexTable, or 0xFFFFFFFF
- * for default NS
- *
1st word : index of attribute name in StringIndexTable
- *
2nd word : index of attribute value, or 0xFFFFFFFF if value is a
- * typed value
- *
3rd word : value type
- *
4th word : resource id value
- *
- */
- private Attribute parseAttribute() {
- final int attrNSIdx = getLEWord(mParserOffset);
- final int attrNameIdx = getLEWord(mParserOffset + (1 * WORD_SIZE));
- final int attrValueIdx = getLEWord(mParserOffset + (2 * WORD_SIZE));
- final int attrType = getLEWord(mParserOffset + (3 * WORD_SIZE));
- final int attrData = getLEWord(mParserOffset + (4 * WORD_SIZE));
-
- final Attribute attr = new Attribute();
- attr.setName(getString(attrNameIdx));
-
- if (attrNSIdx == 0xFFFFFFFF) {
- attr.setNamespace(null);
- attr.setPrefix(null);
- } else {
- String uri = getString(attrNSIdx);
- if (mNamespaces.containsKey(uri)) {
- attr.setNamespace(uri);
- attr.setPrefix(mNamespaces.get(uri));
- }
- }
-
- if (attrValueIdx == 0xFFFFFFFF) {
- attr.setValue(getAttributeValue(attrType, attrData));
- } else {
- attr.setValue(getString(attrValueIdx));
- }
-
- return attr;
-
- }
-
- /**
- * A text will start with the following 4bytes word :
- *
- *
0th word : 0x00100104 = Text
- *
1st word : chunk size
- *
2nd word : line this element appeared in the original document
- *
3rd word : optional xml comment for element (usually 0xFFFFFF)
- *
4rd word : string index in string table
- *
5rd word : ??? (always 8)
- *
6rd word : ??? (always 0)
- *
- */
- private void parseText() {
- // get tag infos
- final int strIndex = getLEWord(mParserOffset + (4 * WORD_SIZE));
-
- String data = getString(strIndex);
- mListener.characterData(data);
-
- // offset to next node
- mParserOffset += (7 * WORD_SIZE);
- }
-
- /**
- * EndTag contains the following 4bytes words :
- *
- *
0th word : 0x00100103 = End_Tag
- *
1st word : chunk size
- *
2nd word : line this tag appeared in the original file
- *
3rd word : optional xml comment for element (usually 0xFFFFFF)
- *
4th word : index of namespace name in StringIndexTable, or 0xFFFFFFFF
- * for default NS
- *
5th word : index of element name in StringIndexTable
- *
- */
- private void parseEndTag() {
- // get tag info
- final int uriIdx = getLEWord(mParserOffset + (4 * WORD_SIZE));
- final int nameIdx = getLEWord(mParserOffset + (5 * WORD_SIZE));
-
- final String name = getString(nameIdx);
- String uri;
- if (uriIdx == 0xFFFFFFFF) {
- uri = "";
- } else {
- uri = getString(uriIdx);
- }
-
- mListener.endElement(uri, name, null);
-
- // offset to start of next tag
- mParserOffset += (6 * WORD_SIZE);
- }
-
- /**
- * @param index the index of the string in the StringIndexTable
- * @return the string
- */
- private String getString(final int index) {
- String res;
- if ((index >= 0) && (index < mStringsCount)) {
- res = mStringsTable[index];
- } else {
- res = null; // NOPMD
- }
-
- return res;
- }
-
- /**
- * @param offset offset of the beginning of the string inside the StringTable
- * (and not the whole data array)
- * @return the String
- */
- private String getStringFromStringTable(final int offset) {
- int strLength;
- byte chars[];
- if (mData[offset + 1] == mData[offset]) {
- strLength = mData[offset];
- chars = new byte[strLength];// NOPMD
- for (int i = 0; i < strLength; i++) {
- chars[i] = mData[offset + 2 + i]; // NOPMD
- }
- } else {
-
- strLength = ((mData[offset + 1] << 8) & 0xFF00)
- | (mData[offset] & 0xFF);
- chars = new byte[strLength]; // NOPMD
- for (int i = 0; i < strLength; i++) {
- chars[i] = mData[offset + 2 + (i * 2)]; // NOPMD
- }
-
- }
- return new String(chars);
- }
-
- /**
- * @param off the offset of the word to read
- * @return value of a Little Endian 32 bit word from the byte arrayat offset
- * off.
- */
- private int getLEWord(final int off) {
- return ((mData[off + 3] << 24) & 0xff000000)
- | ((mData[off + 2] << 16) & 0x00ff0000)
- | ((mData[off + 1] << 8) & 0x0000ff00)
- | ((mData[off + 0] << 0) & 0x000000ff);
- }
-
- /**
- * @param off the offset of the word to read
- * @return value of a Little Endian 16 bit word from the byte array at offset
- * off.
- */
- private int getLEShort(final int off) {
- return ((mData[off + 1] << 8) & 0xff00)
- | ((mData[off + 0] << 0) & 0x00ff);
- }
-
- /**
- * @param type the attribute type
- * @param data the data value
- * @return the typed value
- */
- private String getAttributeValue(final int type, final int data) {
- String res;
-
- switch (type) {
- case TYPE_STRING:
- res = getString(data);
- break;
- case TYPE_DIMEN:
- res = Integer.toString(data >> 8) + DIMEN[data & 0xFF];
- break;
- case TYPE_FRACTION:
- double fracValue = (((double) data) / ((double) 0x7FFFFFFF));
- // res = String.format("%.2f%%", fracValue);
- res = new DecimalFormat("#.##%").format(fracValue);
- break;
- case TYPE_FLOAT:
- res = Float.toString(Float.intBitsToFloat(data));
- break;
- case TYPE_INT:
- case TYPE_FLAGS:
- res = Integer.toString(data);
- break;
- case TYPE_BOOL:
- res = Boolean.toString(data != 0);
- break;
- case TYPE_COLOR:
- case TYPE_COLOR2:
- res = String.format("#%08X", data);
- break;
- case TYPE_ID_REF:
- res = String.format("@id/0x%08X", data);
- break;
- case TYPE_ATTR_REF:
- res = String.format("?id/0x%08X", data);
- break;
- default:
-// Log.w(TAG, "(type=" + Integer.toHexString(type) + ") : " + data
-// + " (0x" + Integer.toHexString(data) + ") @"
-// + mParserOffset);
- res = String.format("%08X/0x%08X", type, data);
- break;
- }
-
- return res;
- }
-
- // Data
- private CompressedXmlParserListener mListener;
-
- // Internal
- private Map mNamespaces;
- private byte[] mData;
-
- private String[] mStringsTable;
- private int[] mResourcesIds;
- private int mStringsCount, mStylesCount, mResCount;
- private int mParserOffset;
-
-}
diff --git a/libs/AXML/src/main/java/fr/xgouchet/axml/CompressedXmlParserListener.java b/libs/AXML/src/main/java/fr/xgouchet/axml/CompressedXmlParserListener.java
deleted file mode 100644
index 2738713d6..000000000
--- a/libs/AXML/src/main/java/fr/xgouchet/axml/CompressedXmlParserListener.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package fr.xgouchet.axml;
-
-public interface CompressedXmlParserListener {
-
- /**
- * Receive notification of the beginning of a document.
- */
- void startDocument();
-
- /**
- * Receive notification of the end of a document.
- */
- void endDocument();
-
- /**
- * Begin the scope of a prefix-URI Namespace mapping.
- *
- * @param prefix
- * the Namespace prefix being declared. An empty string is used
- * for the default element namespace, which has no prefix.
- * @param uri
- * the Namespace URI the prefix is mapped to
- */
- void startPrefixMapping(String prefix, String uri);
-
- /**
- * End the scope of a prefix-URI mapping.
- *
- * @param prefix
- * the prefix that was being mapped. This is the empty string
- * when a default mapping scope ends.
- * @param uri
- * the Namespace URI the prefix is mapped to
- */
- void endPrefixMapping(String prefix, String uri);
-
- /**
- * Receive notification of the beginning of an element.
- *
- * @param uri
- * the Namespace URI, or the empty string if the element has no
- * Namespace URI or if Namespace processing is not being
- * performed
- * @param localName
- * the local name (without prefix), or the empty string if
- * Namespace processing is not being performed
- * @param qName
- * the qualified name (with prefix), or the empty string if
- * qualified names are not available
- * @param atts
- * the attributes attached to the element. If there are no
- * attributes, it shall be an empty Attributes object. The value
- * of this object after startElement returns is undefined
- */
- void startElement(String uri, String localName, String qName,
- Attribute[] atts);
-
- /**
- * Receive notification of the end of an element.
- *
- * @param uri
- * the Namespace URI, or the empty string if the element has no
- * Namespace URI or if Namespace processing is not being
- * performed
- * @param localName
- * the local name (without prefix), or the empty string if
- * Namespace processing is not being performed
- * @param qName
- * the qualified XML name (with prefix), or the empty string if
- * qualified names are not available
- */
- void endElement(String uri, String localName, String qName);
-
- /**
- * Receive notification of text.
- *
- * @param data
- * the text data
- */
- void text(String data);
-
- /**
- * Receive notification of character data (in a <![CDATA[ ]]> block).
- *
- * @param data
- * the text data
- */
- void characterData(String data);
-
- /**
- * Receive notification of a processing instruction.
- *
- * @param target
- * the processing instruction target
- * @param data
- * the processing instruction data, or null if none was supplied.
- * The data does not include any whitespace separating it from
- * the target
- */
- void processingInstruction(String target, String data);
-}
diff --git a/libs/AXML/src/main/java/fr/xgouchet/axml/CompressedXmlUtils.java b/libs/AXML/src/main/java/fr/xgouchet/axml/CompressedXmlUtils.java
deleted file mode 100644
index 5e3d24b31..000000000
--- a/libs/AXML/src/main/java/fr/xgouchet/axml/CompressedXmlUtils.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package fr.xgouchet.axml;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-
-public final class CompressedXmlUtils {
-
- /**
- * @param input an input stream
- * @return if the given input stream looks like an AXML document
- */
- public static boolean isCompressedXml(final InputStream input) {
- if (input == null) return false;
-
- boolean result;
-
- try {
- final byte[] header = new byte[4];
- int read = input.read(header, 0, 4);
- if (read < 4) return false;
-
- result = (header[0] == 0x03)
- && (header[1] == 0x00)
- && (header[2] == 0x08)
- && (header[3] == 0x00);
-
- } catch (Exception e) {
- result = false;
- } finally {
- try {
- input.close();
- } catch (Exception e) {
- // ignore this exception
- }
- }
-
- return result;
- }
-
- /**
- * @param source a source file
- * @return if the given file looks like an AXML file
- */
- public static boolean isCompressedXml(final File source) {
- boolean result;
-
- try {
- final InputStream input = new FileInputStream(source.getPath());
- result = isCompressedXml(input);
- } catch (Exception e) {
- result = false;
- }
-
- return result;
- }
-
- private CompressedXmlUtils() {
- }
-}
diff --git a/monocles.doap b/monocles.doap
new file mode 100644
index 000000000..9fe876acf
--- /dev/null
+++ b/monocles.doap
@@ -0,0 +1,531 @@
+
+
+
+
+ monocles chat
+
+ 2021-06-12
+
+ Android Jabber Client
+
+ monocles chat is a freedomware client for the Jabber network (using the XMPP protocol) with a focus on features for gateway users.
+
+
+
+
+
+
+
+
+
+
+ en
+
+
+
+
+
+
+
+
+
+ Java
+
+ Android
+
+
+
+
+
+
+
+
+ Soprani.ca
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ partial
+ 1.0
+ When used with XEP-0050, images over HTTPS only
+
+
+
+
+
+ partial
+ 1.0
+ For MUC members
+
+
+
+
+
+ complete
+ 0.2.1
+
+
+
+
+
+ partial
+ 0.2.0
+ For XEP-0033 and XEP-0066
+
+
+
+
+
+ partial
+ 0.2.1
+ Receiving, hashes, thumbnails, one HTTP source
+
+
+
+
+
+ complete
+ 1.1.3
+
+
+
+
+
+ partial
+ 2.13.0
+ As part of XEP-0050 and XEP-0077
+
+
+
+
+
+ partial
+ 1.0.2
+ When used with XEP-0050
+
+
+
+
+
+ complete
+ 1.3.0
+
+
+
+
+
+ partial
+ 1.0
+ jabber:iq:gateway
+
+
+
+
+
+ complete
+ 1.4
+
+
+
+
+
+ complete
+ 2.5rc3
+
+
+
+
+
+ complete
+ 1.32.0
+
+
+
+
+
+ complete
+ 1.2
+
+
+
+
+
+ complete
+ 1.2
+
+
+
+
+
+ partial
+ 1.2
+ Avatars only
+
+
+
+
+
+ complete
+ 1.1.3
+
+
+
+
+
+ complete
+ 2.1
+
+
+
+
+
+ complete
+ 1.1
+
+
+
+
+
+ complete
+ 1.5.1
+
+
+
+
+
+ partial
+ 1.1
+ Read only. Publication via XEP-0398
+
+
+
+
+
+ complete
+ 1.2.1
+ Avatar, Nick, OMEMO
+
+
+
+
+
+ complete
+ 1.1.2
+ File transfer + A/V calls
+
+
+
+
+
+ complete
+ 1.2.1
+
+
+
+
+
+ complete
+ 1.1
+ read only
+
+
+
+
+
+ complete
+ 1.1
+
+
+
+
+
+ complete
+ 1.4.0
+
+
+
+
+
+ complete
+ 1.3
+
+
+
+
+
+ complete
+ 1.6
+
+
+
+
+
+ complete
+ 2.0.1
+
+
+
+
+
+ complete
+ 2.0.1
+
+
+
+
+
+ complete
+ 0.7
+
+
+
+
+
+ complete
+ 1.1
+
+
+
+
+
+ complete
+ 0.19.1
+
+
+
+
+
+ complete
+ 1.3
+
+
+
+
+
+ complete
+ 1.0
+
+
+
+
+
+ complete
+ 1.2
+
+
+
+
+
+ complete
+ 1.0.3
+
+
+
+
+
+ complete
+ 1.0
+
+
+
+
+
+ complete
+ 0.13.1
+
+
+
+
+
+ complete
+ 1.0.1
+
+
+
+
+
+ complete
+ 1.0
+
+
+
+
+
+ complete
+ 1.2.0
+
+
+
+
+
+ complete
+ 0.6.3
+
+
+
+
+
+ complete
+ 1.0.2
+ opt-in
+
+
+
+
+
+ complete
+ 1.0.0
+
+
+
+
+
+ complete
+ 0.3
+
+
+
+
+
+ complete
+ 1.0.0
+
+
+
+
+
+ complete
+ 1.0.0
+
+
+
+
+
+ complete
+ 0.3.0
+
+
+
+
+
+ complete
+ 0.3.0
+
+
+
+
+
+ complete
+ 0.4.0
+ Only available in the version distributed over Google Play
+
+
+
+
+
+ complete
+ 1.0.0
+
+
+
+
+
+ complete
+ 1.1.0
+
+
+
+
+
+ complete
+ 0.2
+
+
+
+
+
+ complete
+ 0.3.0
+
+
+
+
+
+ complete
+ 0.1.2
+ 2.5.8
+
+
+
+
+
+ complete
+ 0.6.0
+ 2.3.1
+
+
+
+
+
+ complete
+ 0.1.4
+ 1.22.0
+
+
+
+
+
+ complete
+ 0.1
+ 2.5.8
+
+
+
+
+
+ complete
+ 0.2.1
+
+
+
+
+
+ complete
+ 1.0.1
+ 2.5.4
+
+
+
+
+
+ complete
+ 0.2.0
+
+
+
+
+
+ complete
+ 0.1.0
+
+
+
+
diff --git a/monocles_chat.doap b/monocles_chat.doap
deleted file mode 100644
index 6e37477ae..000000000
--- a/monocles_chat.doap
+++ /dev/null
@@ -1,579 +0,0 @@
-
-
-
-
- monocles chat
-
- 2021-05-04
-
- Userfriendly XMPP Client for Android
-
- monocles chat is a modern and secure chat app using XMPP. Based on Conversations and Blabber.im but with many changes.
-
-
-
-
-
-
-
-
-
-
-
- en
-
-
-
-
-
-
-
-
-
-
-
- Java
-
- Android
-
-
-
-
-
-
-
- Arne-Brün Vogelsang
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- partial
- 1.0
- When used with XEP-0050, images over HTTPS only
-
-
-
-
-
- partial
- 1.0
- For MUC members
-
-
-
-
-
- partial
- 0.2.0
- For XEP-0033 and XEP-0066
-
-
-
-
-
- partial
- 0.2.1
- Receiving, hashes, thumbnails, one HTTP source
-
-
-
-
-
- complete
- 1.1.3
-
-
-
-
-
- partial
- 2.13.0
- As part of XEP-0050 and XEP-0077
-
-
-
-
-
- partial
- 1.0.2
- When used with XEP-0050
-
-
-
-
-
- complete
- 1.3.0
-
-
-
-
-
- partial
- 1.0
- jabber:iq:gateway
-
-
-
-
-
- complete
- 1.4
-
-
-
-
-
- complete
- 2.5rc3
-
-
-
-
-
- complete
- 1.32.0
-
-
-
-
-
- complete
- 1.2
-
-
-
-
-
- complete
- 1.2
-
-
-
-
-
- partial
- 1.2
- Avatars only
-
-
-
-
-
- complete
- 1.1.3
-
-
-
-
-
- complete
- 2.1
-
-
-
-
-
- complete
- 1.1
-
-
-
-
-
- complete
- 1.5.1
-
-
-
-
-
- partial
- 1.1
- Read only. Publication via XEP-0398
-
-
-
-
-
- complete
- 1.2.1
- Avatar, Nick, OMEMO
-
-
-
-
-
- complete
- 1.1.2
- File transfer + A/V calls
-
-
-
-
-
- complete
- 1.2.1
-
-
-
-
-
- complete
- 1.1
- read only
-
-
-
-
-
- complete
- 1.1
-
-
-
-
-
- complete
- 1.4.0
-
-
-
-
-
- complete
- 1.3
-
-
-
-
-
- complete
- 1.6
-
-
-
-
-
- complete
- 2.0.1
-
-
-
-
-
- complete
- 2.0.1
-
-
-
-
-
- complete
- 0.7
-
-
-
-
-
- partial
- 1.0
- When used with XEP-0050, images over HTTPS only
-
-
-
-
-
- complete
- 1.1
-
-
-
-
-
- complete
- 0.19.1
-
-
-
-
-
- complete
- 1.3
-
-
-
-
-
- complete
- 1.0
-
-
-
-
-
- complete
- 1.2
-
-
-
-
-
- complete
- 1.0.3
-
-
-
-
-
- complete
- 1.0
-
-
-
-
-
- complete
- 0.13.1
-
-
-
-
-
- complete
- 1.0.1
-
-
-
-
-
- complete
- 1.0
-
-
-
-
-
- complete
- 1.2.0
-
-
-
-
-
- complete
- 0.6.3
-
-
-
-
-
- complete
- 1.0.2
- opt-in
-
-
-
-
-
- complete
- 1.0.0
-
-
-
-
-
- complete
- 0.3
-
-
-
-
-
- complete
- 1.0.0
-
-
-
-
-
- complete
- 1.0.0
-
-
-
-
-
- complete
- 0.3.0
-
-
-
-
-
- complete
- 0.3.0
-
-
-
-
-
- complete
- 0.4.0
- Only available in the version distributed over Google Play
-
-
-
-
-
- complete
- 1.0.0
-
-
-
-
-
- complete
- 1.1.0
-
-
-
-
-
- complete
- 0.3.1
-
-
-
-
-
- complete
- 0.3.0
-
-
-
-
-
- complete
- 0.1.2
- 1.0
-
-
-
-
-
- complete
- 0.6.0
- 1.0
-
-
-
-
-
- complete
- 0.1.4
- 1.0
-
-
-
-
-
- complete
- 0.1
- 1.0
-
-
-
-
-
- complete
- 0.2.1
-
-
-
-
-
- complete
- 1.0.1
- 1.0
-
-
-
-
-
- complete
- 0.2.0
-
-
-
-
-
- complete
- 0.2.1
-
-
-
-
-
- complete
- 0.4.0
-
-
-
-
-
- complete
- 0.1.0
-
-
-
-
-
- complete
- 0.1.0
-
-
-
-
-
- partial
- 0.3.2
- 1.4.3
-
-
-
-
-
- complete
- 0.1.0
-
-
-
-
-
- 1.7.10
- 2024-06-01
-
-
-
-
-
diff --git a/monocleschatFree/debug/output-metadata.json b/monocleschatFree/debug/output-metadata.json
new file mode 100644
index 000000000..a3bb9e6ac
--- /dev/null
+++ b/monocleschatFree/debug/output-metadata.json
@@ -0,0 +1,73 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "de.monocles.chat",
+ "variantName": "monocleschatFreeDebug",
+ "elements": [
+ {
+ "type": "UNIVERSAL",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 4209171,
+ "versionName": "29082ff+free",
+ "outputFile": "Conversations-monocleschat-free-universal-debug.apk"
+ },
+ {
+ "type": "ONE_OF_MANY",
+ "filters": [
+ {
+ "filterType": "ABI",
+ "value": "arm64-v8a"
+ }
+ ],
+ "attributes": [],
+ "versionCode": 4205304,
+ "versionName": "29082ff+free",
+ "outputFile": "Conversations-monocleschat-free-arm64-v8a-debug.apk"
+ },
+ {
+ "type": "ONE_OF_MANY",
+ "filters": [
+ {
+ "filterType": "ABI",
+ "value": "x86"
+ }
+ ],
+ "attributes": [],
+ "versionCode": 4205302,
+ "versionName": "29082ff+free",
+ "outputFile": "Conversations-monocleschat-free-x86-debug.apk"
+ },
+ {
+ "type": "ONE_OF_MANY",
+ "filters": [
+ {
+ "filterType": "ABI",
+ "value": "armeabi-v7a"
+ }
+ ],
+ "attributes": [],
+ "versionCode": 4205301,
+ "versionName": "29082ff+free",
+ "outputFile": "Conversations-monocleschat-free-armeabi-v7a-debug.apk"
+ },
+ {
+ "type": "ONE_OF_MANY",
+ "filters": [
+ {
+ "filterType": "ABI",
+ "value": "x86_64"
+ }
+ ],
+ "attributes": [],
+ "versionCode": 4205303,
+ "versionName": "29082ff+free",
+ "outputFile": "Conversations-monocleschat-free-x86_64-debug.apk"
+ }
+ ],
+ "elementType": "File",
+ "minSdkVersionForDexing": 23
+}
\ No newline at end of file
diff --git a/playstore/release/output-metadata.json b/playstore/release/output-metadata.json
deleted file mode 100644
index 99887c851..000000000
--- a/playstore/release/output-metadata.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "version": 3,
- "artifactType": {
- "type": "APK",
- "kind": "Directory"
- },
- "applicationId": "de.monocles.chat",
- "variantName": "playstoreRelease",
- "elements": [
- {
- "type": "UNIVERSAL",
- "filters": [],
- "attributes": [],
- "versionCode": 138,
- "versionName": "4135a60-playstore",
- "outputFile": "monocles chat-1.7.6-playstore-universal-release.apk"
- },
- {
- "type": "ONE_OF_MANY",
- "filters": [
- {
- "filterType": "ABI",
- "value": "arm64-v8a"
- }
- ],
- "attributes": [],
- "versionCode": 13804,
- "versionName": "4135a60-playstore",
- "outputFile": "monocles chat-1.7.6-playstore-arm64-v8a-release.apk"
- },
- {
- "type": "ONE_OF_MANY",
- "filters": [
- {
- "filterType": "ABI",
- "value": "armeabi-v7a"
- }
- ],
- "attributes": [],
- "versionCode": 13801,
- "versionName": "4135a60-playstore",
- "outputFile": "monocles chat-1.7.6-playstore-armeabi-v7a-release.apk"
- },
- {
- "type": "ONE_OF_MANY",
- "filters": [
- {
- "filterType": "ABI",
- "value": "x86_64"
- }
- ],
- "attributes": [],
- "versionCode": 13803,
- "versionName": "4135a60-playstore",
- "outputFile": "monocles chat-1.7.6-playstore-x86_64-release.apk"
- },
- {
- "type": "ONE_OF_MANY",
- "filters": [
- {
- "filterType": "ABI",
- "value": "x86"
- }
- ],
- "attributes": [],
- "versionCode": 13802,
- "versionName": "4135a60-playstore",
- "outputFile": "monocles chat-1.7.6-playstore-x86-release.apk"
- }
- ],
- "elementType": "File"
-}
\ No newline at end of file
diff --git a/proguard-rules.pro b/proguard-rules.pro
index 75902f079..7098945c6 100644
--- a/proguard-rules.pro
+++ b/proguard-rules.pro
@@ -1,7 +1,5 @@
-dontobfuscate
--keep class de.monocles.chat.**
--keep class de.pixart.messenger.**
-keep class eu.siacs.conversations.**
-keep class org.whispersystems.**
@@ -15,9 +13,6 @@
-keep class org.openintents.openpgp.*
-keep class org.webrtc.** { *; }
--keep class com.squareup.okhttp.** { *; }
--keep interface com.squareup.okhttp.** { *; }
-
-dontwarn javax.mail.internet.MimeMessage
-dontwarn javax.mail.internet.MimeBodyPart
-dontwarn javax.mail.internet.SharedInputStream
@@ -39,25 +34,12 @@
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE
+-dontwarn org.jetbrains.annotations.**
-keepclassmembers class eu.siacs.conversations.http.services.** {
!transient ;
}
-# JSR 305 annotations are for embedding nullability information.
--dontwarn javax.annotation.**
-
-# A resource is loaded with a relative path so the package of this class must be preserved.
--keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-
-# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
--dontwarn org.codehaus.mojo.animal_sniffer.*
-
-# OkHttp platform used only on JVM and when Conscrypt dependency is available.
--dontwarn okhttp3.internal.platform.ConscryptPlatform
-
-
-
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
diff --git a/settings.gradle b/settings.gradle
index 609b97313..4193570fa 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1 @@
-include ':libs:AXML'
-include ':libs:xmpp-addr'
-rootProject.name = 'monocles chat'
+rootProject.name = 'Conversations'
diff --git a/src/androidTest/java/de/monocles/chat/test/ScreenshotTest.java b/src/androidTest/java/de/monocles/chat/test/ScreenshotTest.java
new file mode 100644
index 000000000..a35c96379
--- /dev/null
+++ b/src/androidTest/java/de/monocles/chat/test/ScreenshotTest.java
@@ -0,0 +1,169 @@
+package de.monocles.chat.test;
+
+import java.util.concurrent.TimeoutException;
+import java.lang.Thread;
+import java.util.Arrays;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ServiceTestRule;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import tools.fastlane.screengrab.Screengrab;
+import tools.fastlane.screengrab.cleanstatusbar.CleanStatusBar;
+import tools.fastlane.screengrab.locale.LocaleTestRule;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.ServiceDiscoveryResult;
+import eu.siacs.conversations.entities.TransferablePlaceholder;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.test.R;
+import eu.siacs.conversations.ui.ConversationsActivity;
+import eu.siacs.conversations.ui.StartConversationActivity;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.pep.Avatar;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+@RunWith(AndroidJUnit4.class)
+public class ScreenshotTest {
+
+ static String pkg = InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+ static XmppConnectionService xmppConnectionService;
+ static Account account;
+
+ @ClassRule
+ public static final LocaleTestRule localeTestRule = new LocaleTestRule();
+
+ @ClassRule
+ public static final ServiceTestRule xmppServiceRule = new ServiceTestRule();
+
+ @BeforeClass
+ public static void setup() throws TimeoutException {
+ CleanStatusBar.enableWithDefaults();
+
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), XmppConnectionService.class);
+ intent.setAction("ui");
+ xmppConnectionService = ((XmppConnectionBinder) xmppServiceRule.bindService(intent)).getService();
+ account = xmppConnectionService.findAccountByJid(Jid.of("carrot@chaosah.hereva"));
+ if (account == null) {
+ account = new Account(
+ Jid.of("carrot@chaosah.hereva"),
+ "orangeandfurry"
+ );
+ xmppConnectionService.createAccount(account);
+ }
+
+ Uri avatarUri = Uri.parse("android.resource://" + pkg + "/" + String.valueOf(R.drawable.carrot));
+ final Avatar avatar = xmppConnectionService.getFileBackend().getPepAvatar(avatarUri, 192, Bitmap.CompressFormat.WEBP);
+ xmppConnectionService.getFileBackend().save(avatar);
+ account.setAvatar(avatar.getFilename());
+
+ Contact cheogram = account.getRoster().getContact(Jid.of("cheogram.com"));
+ cheogram.setOption(Contact.Options.IN_ROSTER);
+ cheogram.setPhotoUri("android.resource://" + pkg + "/" + String.valueOf(R.drawable.cheogram));
+ Presence cheogramPresence = Presence.parse(null, null, "");
+ IqPacket discoPacket = new IqPacket(IqPacket.TYPE.RESULT);
+ Element query = discoPacket.addChild("query", "http://jabber.org/protocol/disco#info");
+ Element identity = query.addChild("identity");
+ identity.setAttribute("category", "gateway");
+ identity.setAttribute("type", "pstn");
+ cheogramPresence.setServiceDiscoveryResult(new ServiceDiscoveryResult(discoPacket));
+ cheogram.updatePresence("gw", cheogramPresence);
+ }
+
+ @AfterClass
+ public static void teardown() {
+ CleanStatusBar.disable();
+ }
+
+ @Test
+ public void testConversation() throws FileBackend.FileCopyException, InterruptedException {
+ Conversation conversation = xmppConnectionService.findOrCreateConversation(account, Jid.of("+15550737737@cheogram.com"), false, false);
+ conversation.getContact().setOption(Contact.Options.IN_ROSTER);
+ conversation.getContact().setSystemName("Pepper");
+ conversation.getContact().setPhotoUri("android.resource://" + pkg + "/" + String.valueOf(R.drawable.pepper));
+
+ Message voicemail = new Message(conversation, "", 0, Message.STATUS_RECEIVED);
+ voicemail.setOob("https://example.com/thing.mp3");
+ voicemail.setFileParams(new Message.FileParams("https://example.com/thing.mp3|5000|0|0|10000"));
+ voicemail.setType(Message.TYPE_FILE);
+ voicemail.setSubject("Voicemail Recording");
+
+ Message transcript = new Message(conversation, "Where are you?", 0, Message.STATUS_RECEIVED);
+ transcript.setSubject("Voicemail Transcription");
+
+ Message picture = new Message(conversation, "", 0, Message.STATUS_SEND_RECEIVED);
+ picture.setOob("https://example.com/thing.webp");
+ picture.setType(Message.TYPE_FILE);
+ xmppConnectionService.getFileBackend().copyFileToPrivateStorage(
+ picture,
+ Uri.parse("android.resource://" + pkg + "/" + String.valueOf(R.drawable.komona)),
+ "image/webp"
+ );
+ xmppConnectionService.getFileBackend().updateFileParams(picture);
+
+ conversation.addAll(0, Arrays.asList(
+ voicemail,
+ transcript,
+ new Message(conversation, "Meow", 0, Message.STATUS_SEND_RECEIVED),
+ picture,
+ new Message(conversation, "👍", 0, Message.STATUS_RECEIVED)
+ ));
+
+ ActivityScenario scenario = ActivityScenario.launch(ConversationsActivity.class);
+ scenario.onActivity((Activity activity) -> {
+ ((ConversationsActivity) activity).switchToConversation(conversation);
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ Thread.sleep(100); // ImageView not paited yet after waitForIdleSync
+ Screengrab.screenshot("conversation");
+ }
+
+ @Test
+ public void testStartConversation() throws InterruptedException {
+ ActivityScenario scenario = ActivityScenario.launch(StartConversationActivity.class);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ Thread.sleep(100); // ImageView not paited yet after waitForIdleSync
+ Screengrab.screenshot("startConversation");
+ }
+
+ @Test
+ public void testAddContact() throws InterruptedException {
+ ActivityScenario scenario = ActivityScenario.launch(StartConversationActivity.class);
+ onView(withId(eu.siacs.conversations.R.id.speed_dial)).perform(click());
+ Screengrab.screenshot("startConversationOptions");
+
+ // Not actually online, so can't screenshot the gateway selector yet
+ /*onView(withId(eu.siacs.conversations.R.id.create_contact)).perform(click());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ Thread.sleep(10000); // ImageView not paited yet after waitForIdleSync
+ Screengrab.screenshot("addContact");*/
+ }
+}
diff --git a/src/androidTest/res/drawable/carrot.webp b/src/androidTest/res/drawable/carrot.webp
new file mode 100644
index 000000000..cf0cd1288
Binary files /dev/null and b/src/androidTest/res/drawable/carrot.webp differ
diff --git a/src/androidTest/res/drawable/cheogram.png b/src/androidTest/res/drawable/cheogram.png
new file mode 100644
index 000000000..ff4b5cf9c
Binary files /dev/null and b/src/androidTest/res/drawable/cheogram.png differ
diff --git a/src/androidTest/res/drawable/komona.webp b/src/androidTest/res/drawable/komona.webp
new file mode 100644
index 000000000..19cb20d1d
Binary files /dev/null and b/src/androidTest/res/drawable/komona.webp differ
diff --git a/src/androidTest/res/drawable/pepper.webp b/src/androidTest/res/drawable/pepper.webp
new file mode 100644
index 000000000..fd3d822f0
Binary files /dev/null and b/src/androidTest/res/drawable/pepper.webp differ
diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml
new file mode 100644
index 000000000..5b111101e
--- /dev/null
+++ b/src/conversations/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/conversations/fastlane/metadata/android/da-DK/short_description.txt b/src/conversations/fastlane/metadata/android/da-DK/short_description.txt
new file mode 100644
index 000000000..166643398
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/da-DK/short_description.txt
@@ -0,0 +1 @@
+Krypteret, brugervenlig XMPP instant messenger til din mobile enhed
diff --git a/src/conversations/fastlane/metadata/android/de-DE/full_description.txt b/src/conversations/fastlane/metadata/android/de-DE/full_description.txt
new file mode 100644
index 000000000..d6b5d4b1e
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/de-DE/full_description.txt
@@ -0,0 +1,39 @@
+Einfach zu bedienen, zuverlässig, batteriefreundlich. Mit integrierter Unterstützung für Bilder, Gruppenchats und E2E-Verschlüsselung.
+
+Designprinzipien:
+
+* Möglichst schön und benutzerfreundlich, ohne Abstriche bei der Sicherheit und Privatsphäre
+* Auf bestehende, gut etablierte Protokolle zurückgreifen
+* Kein Google-Konto oder speziell Google Cloud Messaging (GCM) erforderlich
+* So wenig Berechtigungen wie möglich erfordern
+
+Funktionen:
+
+* Ende-zu-Ende-Verschlüsselung entweder mit OMEMO oder OpenPGP
+* Senden und Empfangen von Bildern
+* Verschlüsselte Audio- und Videoanrufe (DTLS-SRTP)
+* Intuitives UI, das den Android Design Richtlinien folgt
+* Bilder / Profilbilder für deine Kontakte
+* Synchronisation mit Desktop-Client
+* Konferenzen (mit Unterstützung für Lesezeichen)
+* Adressbucheinbindung
+* Mehrere Konten / einheitlicher Posteingang
+* Sehr geringe Auswirkungen auf die Akkulaufzeit
+
+Mit Conversations ist es sehr einfach, ein Konto auf dem kostenlosen conversations.im-Server zu erstellen. Dennoch funktioniert Conversations auch mit jedem anderen XMPP-Server. Zahlreiche XMPP-Server werden von Freiwilligen betrieben und sind kostenlos.
+
+XMPP-Funktionen:
+
+Conversations funktioniert mit jedem XMPP-Server. XMPP ist jedoch ein erweiterbares Protokoll. Diese Erweiterungen sind ebenfalls in sogenannten XEP's standardisiert. Conversations unterstützt einige davon, um die Benutzerfreundlichkeit zu verbessern. Es besteht die Möglichkeit, dass Ihr aktueller XMPP-Server diese Erweiterungen nicht unterstützt. Um Conversations optimal nutzen zu können, solltest du daher entweder zu einem XMPP-Server wechseln, der dies unterstützt, oder - noch besser - einen eigenen XMPP-Server für dich und deine Freunde betreiben.
+
+Diese XEPs sind es derzeit:
+
+* XEP-0065: SOCKS5 Bytestreams (oder mod_proxy65). Wird für die Übertragung von Dateien verwendet, wenn sich beide Parteien hinter einer Firewall (NAT) befinden.
+* XEP-0163: Personal Eventing Protocol für Profilbilder
+* XEP-0191: Mit dem Blockierungsbefehl kannst du Spammer auf eine schwarze Liste setzen oder Kontakte blockieren, ohne sie aus deiner Liste zu entfernen.
+* XEP-0198: Stream Management ermöglicht es XMPP, kleinere Netzwerkausfälle und Änderungen der zugrunde liegenden TCP-Verbindung zu überstehen.
+* XEP-0280: Message Carbons, das die von dir gesendeten Nachrichten automatisch mit deinem Desktop-Client synchronisiert und es dir somit ermöglicht, innerhalb einer Unterhaltung nahtlos von deinem mobilen Client zu deinem Desktop-Client und zurück zu wechseln.
+* XEP-0237: Roster Versioning hauptsächlich, um Bandbreite bei schlechten mobilen Verbindungen zu sparen
+* XEP-0313: Nachrichtenarchiv-Management synchronisiert den Nachrichtenverlauf mit dem Server. Aufholen von Nachrichten, die gesendet wurden, während Conversations offline war.
+* XEP-0352: Client State Indication lässt den Server wissen, ob Conversations im Hintergrund läuft oder nicht. Ermöglicht es dem Server, Bandbreite zu sparen, indem er unwichtige Pakete zurückhält.
+* XEP-0363: HTTP File Upload ermöglicht den Austausch von Dateien in Konferenzen und mit Offline-Kontakten. Erfordert eine zusätzliche Komponente auf deinem Server.
diff --git a/src/conversations/fastlane/metadata/android/de-DE/short_description.txt b/src/conversations/fastlane/metadata/android/de-DE/short_description.txt
new file mode 100644
index 000000000..5249bddf2
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/de-DE/short_description.txt
@@ -0,0 +1 @@
+Verschlüsselter, benutzerfreundlicher XMPP-Instant-Messenger für dein Smartphone
diff --git a/src/conversations/fastlane/metadata/android/en-US/full_description.txt b/src/conversations/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 000000000..998dc6832
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,19 @@
+The monocles chat app allows you to join a worldwide communication network. It especially focuses on features useful to users who want to contact those on other networks as well, such as SMS-enabled phone numbers.
+
+Based on the app Conversations, but with unique features:
+
+* Messages with both media and text, including animated media
+* Unobtrusive display of subject lines, where present
+* Links to known contacts are shown with their name
+* Show timestamps for calls
+* Integrates with gateways' add contact flows
+* When using a gateway to the phone network, integrate with the native Android Phone app
+* Address book integration
+* Tag contacts and channels and browse by tag
+* Command UI
+
+Where to get service:
+
+monocles chat requires you have an account with a Jabber service. You can run your own service, or use one provided by someone else, for example: https://snikket.org/hosting/
+
+Art in screenshots is from https://www.peppercarrot.com by David Revoy, CC-BY. Artwork has been modified to crop out sections for avatars and photos, and in some cases add transparency. Use of this artwork does not imply endorsement of this project by the artist.
diff --git a/src/conversations/fastlane/metadata/android/en-US/images/icon.png b/src/conversations/fastlane/metadata/android/en-US/images/icon.png
new file mode 100644
index 000000000..046dbfb3a
Binary files /dev/null and b/src/conversations/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/src/conversations/fastlane/metadata/android/en-US/short_description.txt b/src/conversations/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 000000000..cfce968a0
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+Chat on the Jabber network (XMPP protocol); focus on features for gateway users
diff --git a/src/conversations/fastlane/metadata/android/eo/short_description.txt b/src/conversations/fastlane/metadata/android/eo/short_description.txt
new file mode 100644
index 000000000..514cca5bd
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/eo/short_description.txt
@@ -0,0 +1 @@
+Ĉifrita, facile uzebla XMPP tujmesaĝilo por via poŝtelefono
diff --git a/src/conversations/fastlane/metadata/android/es-ES/full_description.txt b/src/conversations/fastlane/metadata/android/es-ES/full_description.txt
new file mode 100644
index 000000000..853549547
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/es-ES/full_description.txt
@@ -0,0 +1,39 @@
+Fácil de usar, fiable y con poca batería. Con soporte integrado para imágenes, chats de grupo y cifrado e2e.
+
+Principios de diseño:
+
+* Ser lo más bonito y fácil de usar posible sin sacrificar la seguridad ni la privacidad.
+* Basarse en protocolos existentes y bien establecidos.
+* No requerir una cuenta de Google o, específicamente, Google Cloud Messaging (GCM).
+* Requerir el menor número de permisos posible
+
+Características:
+
+* Cifrado de extremo a extremo con OMEMO o OpenPGP.
+* Envío y recepción de imágenes
+* Llamadas de audio y vídeo cifradas (DTLS-SRTP)
+* Interfaz de usuario intuitiva que sigue las directrices de diseño de Android
+* Imágenes / Avatares para tus contactos
+* Sincronización con el cliente de escritorio
+* Conferencias (con soporte para marcadores)
+* Integración de la libreta de direcciones
+* Múltiples cuentas / bandeja de entrada unificada
+* Muy bajo impacto en la duración de la batería
+
+Conversations hace que sea muy fácil crear una cuenta en el servidor gratuito conversations.im. Sin embargo, Conversations también funciona con cualquier otro servidor XMPP. Muchos servidores XMPP están gestionados por voluntarios y son gratuitos.
+
+Características de XMPP:
+
+Conversations funciona con todos los servidores XMPP existentes. Sin embargo, XMPP es un protocolo extensible. Estas extensiones también están estandarizadas en los llamados XEP. Conversations soporta un par de ellas para mejorar la experiencia general del usuario. Existe la posibilidad de que su actual servidor XMPP no soporte estas extensiones. Por lo tanto, para sacar el máximo provecho de Conversaciones deberías considerar o bien cambiar a un servidor XMPP que lo haga o - mejor aún - ejecutar tu propio servidor XMPP para ti y tus amigos.
+
+Estos XEPs son (por el momento):
+
+* XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Se utilizará para transferir archivos si ambas partes están detrás de un cortafuegos (NAT).
+* XEP-0163: Protocolo de Evento Personal para avatares
+* XEP-0191: El comando de bloqueo te permite hacer una lista negra de spammers o bloquear contactos sin eliminarlos de tu lista.
+* XEP-0198: Stream Management permite a XMPP sobrevivir a pequeños cortes de red y cambios de la conexión TCP subyacente.
+* XEP-0280: Message Carbons que sincroniza automáticamente los mensajes que envías a tu cliente de escritorio y por lo tanto te permite cambiar sin problemas de tu cliente móvil a tu cliente de escritorio y viceversa en una sola conversación.
+* XEP-0237: Versionado de listas, principalmente para ahorrar ancho de banda en conexiones móviles deficientes.
+* XEP-0313: Gestión de Archivo de Mensajes sincroniza el historial de mensajes con el servidor. Ponerse al día con los mensajes que fueron enviados mientras Conversaciones estaba fuera de línea.
+* XEP-0352: Indicación del Estado del Cliente permite al servidor saber si Conversaciones está o no en segundo plano. Permite al servidor ahorrar ancho de banda reteniendo paquetes sin importancia.
+* XEP-0363: Carga de Archivos HTTP permite compartir archivos en conferencias y con contactos sin conexión. Requiere un componente adicional en su servidor.
diff --git a/src/conversations/fastlane/metadata/android/es-ES/short_description.txt b/src/conversations/fastlane/metadata/android/es-ES/short_description.txt
new file mode 100644
index 000000000..7ed047672
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/es-ES/short_description.txt
@@ -0,0 +1 @@
+Mensajería instantánea XMPP cifrada y fácil de usar para tu dispositivo móvil
diff --git a/src/conversations/fastlane/metadata/android/fi-FI/full_description.txt b/src/conversations/fastlane/metadata/android/fi-FI/full_description.txt
new file mode 100644
index 000000000..8071c92d0
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/fi-FI/full_description.txt
@@ -0,0 +1,39 @@
+Helppokäyttöinen, luotettava ja vähän akkua käyttävä. Sisäänrakennettu tuki kuville, ryhmille ja päästä päähän -salaukselle.
+
+Periaatteet:
+
+* Ole niin kaunis ja helppokäyttöinen kuin on mahdolista turvallisuudesta ja ykstyisyydestä tinkimättä
+* Käytä valmiita ja vakiintuneita protokollia
+* Älä riipu Google-tunnuksesta äläkä Google Cloud Messaging -palvelusta (GCM)
+* Vaadi niin vähän käyttöoikeuksia kuin mahdollista
+
+Ominaisuudet:
+
+* Päästä päähän -salaus joko OMEMO:lla tai OpenPGP:llä
+* Lähetä ja vastaanota kuvia
+* Salatut ääni- ja videopuhelut (DTLS-SRTP)
+* Intuitiivinen käyttöliittymä joka noudattaa Androidin muotoilukieltä
+* Profiilikuvat yhteystiedoille
+* Synkronoi työpöytäversion kanssa
+* Konferenssit (kirjanmerkkituella)
+* Osoitekirjaintegrointi
+* Useampi tili yhdessä näkymässä
+* Todella pieni akun kulutus
+
+Conversations:lla on helppo luoda tili conversations.im-palvelimella. Silti Conversations toimii myös minkä tahansa muun XMPP-palvelimen kanssa. Monia XMPP-palvelimia ylläpidetään ilmaiseksi vapaaehtoisvoimin.
+
+XMPP-ominaisuudet:
+
+Conversations toimii kaikkien XMPP-palvelinten kanssa. XMPP on kuitenkin laajennettava protokolla. Nämä laajennukset on standardoitu niin kutsuttuina XEP:inä. Conversations tukee muutamaa näistä tehdäkseen käyttäjäkokemuksesta paremman. On mahdollista että nykyinen XMPP-palvelimesi ei tue kaikkia näitä laajennoksia. Siispä saadaksesi kaiken ilon irti Conversationsista kannattaa harkita joko sellaiseen palvelimeen, joka tukee näitä, vaihtamista tai oman XMPP-palvelimen ylläpitämistä itsellesi ja kavereillesi.
+
+XEP:t ovat tällä hetkellä:
+
+* XEP-0065: SOCKS5 Bytestreams (tai mod_proxy65). Käytetään tiedostojen siirtoon jos molemmat osapuolet ovat palomuurin tai NAT:n takana.
+* XEP-0163: Personal Eventing Protocol profiilikuville
+* XEP-0191: Blocking command lets you blacklist spammers or block contacts without removing them from your roster.
+* XEP-0198: Stream Management mahdollistaa XMPP:n selviämisen pienestä verkon pätkimisestä ja TCP-yhteyden muutoksista.
+* XEP-0280: Kopiot lähettämistäsi viesteistä muille laitteillesi. Mahdolistaa laitteiden vaihdon kesken keskustelun täysin saumoitta.
+* XEP-0237: Roster Versioning säästää dataa heikoila yhteyksillä
+* XEP-0313: Message Archive Management synchronize message history with the server. Catch up with messages that were sent while Conversations was offline.
+* XEP-0352: Client State Indication lets the server know whether or not Conversations is in the background. Allows the server to save bandwidth by withholding unimportant packages.
+* XEP-0363: HTTP File Upload allows you to share files in conferences and with offline contacts. Requires an additional component on your server.
diff --git a/src/conversations/fastlane/metadata/android/fi-FI/short_description.txt b/src/conversations/fastlane/metadata/android/fi-FI/short_description.txt
new file mode 100644
index 000000000..2713b6efd
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/fi-FI/short_description.txt
@@ -0,0 +1 @@
+Salattu ja helppokäyttöinen XMPP-pikaviestin mobiililaitteellesi
diff --git a/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt b/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt
new file mode 100644
index 000000000..653bdec10
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/fr-FR/full_description.txt
@@ -0,0 +1,38 @@
+Facile à utiliser, fiable, respectueux de votre batterie. Prend en charge les images, les conversations de groupe et le chiffrement de bout-en-bout.
+
+Principes de conception :
+
+* Être le plus joli et simple d'utilisation possible, sans compromis sur la sécurité ou la vie privée.
+* S'appuyer sur des protocoles existants, bien établis
+* Ne pas nécessiter de compte Google ou spécifiquement le Google Cloud Messaging (GCM)
+* Nécessiter le moins de permissions possible
+
+Fonctionnalités :
+* Chiffrement de bout-en-bout avec au choix, OMEMO ou OpenPGP
+* Envoi et réception d'images
+* Appels audio et vidéo chiffrés (DTLS-SRTP)
+* Interface utilisateur intuitive qui suit les directives de conception d'Android
+* Images / avatars pour vos contacts
+* Synchronisation avec des clients de bureau
+* Conférences (avec prise en charge des marque-pages)
+* Intégration avec le carnet d'adresses
+* Plusieurs comptes / boîte de réception unifiée
+* Impact très faible sur l'autonomie de la batterie
+
+Conversations facilite la création de compte sur le serveur gratuit conversations.im. Cependant Conversations fonctionne également avec n'importe quel autre serveur XMPP. De nombreux serveurs XMPP sont gérés par des bénévoles et gratuits.
+
+Fonctionnalités de XMPP :
+
+Conversations fonctionne avec n'importe quel serveur XMPP. Cependant XMPP est un protocole extensible. Ces extensions sont aussi standardisées dans ce que l'on appelle les XEP. Conversations en prend en charge quelques-unes pour rendre l'expérience utilisateur meilleure dans l'ensemble. Il y a des chances que votre serveur XMPP actuel ne les prenne pas en charge. Ainsi, pour tirer le meilleur parti de Conversations, vous devriez envisager soit de passer à un serveur XMPP qui le fait, ou encore mieux, gérer votre propre serveur XMPP pour vous et vos amis.
+
+Ces XEP sont actuellement :
+
+* XEP-0065: SOCKS5 Bytestreams (ou mod_proxy65). Sera utilisé pour transférer des fichiers si les deux correspondants sont derrière un pare-feu (NAT).
+* XEP-0163: Personal Eventing Protocol pour les avatars
+* XEP-0191: Blocking Command vous permet de mettre des spammeurs sur liste noire ou bloquer des contacts sans les retirer de vos contacts.
+* XEP-0198: Stream Management permet à XMPP de survivre à des petites pannes de réseau et aux changements de la connexion TCP sous-jacente.
+* XEP-0280: Message Carbons qui synchronise automatiquement les messages que vous envoyez à votre client de bureau et vous permet ainsi de passer sans heurt de votre client mobile à votre client de bureau et inversement dans une conversation.
+* XEP-0237: Roster Versioning principalement pour économiser de la bande passante sur les connexions mobiles de mauvaise qualité.
+* XEP-0313: Message Archive Management synchronise l'historique des messages avec le serveur. Retrouvez des messages qui ont été envoyés pendant que Conversations était hors ligne.
+* XEP-0352: Client State Indication fait savoir au serveur si Conversations est ou n'est pas en arrière-plan. Permet au serveur d'économiser de la bande passante en différant des paquets non importants.
+* XEP-0363: HTTP File Upload vous permet de partager des fichiers dans les conférences et avec des contacts hors-ligne. Nécessite un composant supplémentaire sur votre serveur.
diff --git a/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt b/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt
new file mode 100644
index 000000000..b4ae66d63
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/fr-FR/short_description.txt
@@ -0,0 +1 @@
+Messagerie instantanée XMPP chiffrée, facile à utiliser avec votre appareil mobile
diff --git a/src/conversations/fastlane/metadata/android/gl-ES/full_description.txt b/src/conversations/fastlane/metadata/android/gl-ES/full_description.txt
new file mode 100644
index 000000000..9bdcf9042
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/gl-ES/full_description.txt
@@ -0,0 +1,40 @@
+Fácil de usar, fiable, baixo consumo de batería. Con soporte para imaxes, conversas en grupo e cifraxe e2e.
+
+Principios do deseño:
+
+* Ser tan fermosa e doada de usar como sexa posible sen sacrificar a seguridade ou privacidade
+* Apoiarse en protocolos existentes e ben establecidos
+* Non precisar dunha Conta de Google ou concretamente Google Cloud Messaging (GCM)
+* Solicitar os mínimos permisos posibles
+
+Características:
+
+* Cifraxe extremo-a-extremo, ben con OMEMO ou con OpenPGP
+* Enviar e recibir imaxes
+* Chamadas de audio e vídeo cifradas (DTLS-SRTP)
+* Interface intuitiva seguindo as recomendacións Android Design
+* Imaxes/Avatares para os Contactos
+* Sicronizada co cliente de escritorio
+* Conferencias (con soporte para marcadores)
+* Integración coa Libreta de enderezos
+* Varias contas cunha lista de conversas unificada
+* Consumo de enerxía moi baixo
+
+Con Conversations é moi doado crear unha conta no servidor gratuíto conversations.im. Con todo, Conversations funcionará igualmente con calquera outro servidor XMPP. Existen moitos servidores XMPP xestionados por voluntarios e gratuítos.
+
+Características de XMPP:
+
+Conversations funciona con calquera sevidor XMPP, mais XMPP é un protocolo extensible. Estas extensións tamén están estadarizadas nos chamados XEP's.
+Conversations da soporte a un par delas que axudan a mellorar a experiencia de uso da aplicación. Pode acontecer que o teu servidor XMPP actual non dé soporte para estas extensións. Por tanto para obter o mellor resultado ao usar Conversations debes ter considerar usar un servidor XMPP que si o faga - ou incluso mellor - xestionar o teu propio servidor para as túas amizades.
+
+Estes XEPs son - neste intre:
+
+* XEP-0065: SOCKS5 Bytestreams (ou mod_proxy65). Usado para a transferencia de ficheiros se as dúas partes están detrás dun cortalumes (NAT).
+* XEP-0163: Personal Eventing Protocol para os avatares
+* XEP-0191: O bloqueo de ordes permiteche bloquear spammer ou contactos sen eliminalos das túas listaxes.
+* XEP-0198: Stream Management permite que XMPP sobreviva a caídas da rede e cambios na conexión TCP.
+* XEP-0280: Message Carbons permite sincronizar automáticamente as mensaxes co teu cliente de escritorio e por tanto cambiar dun a outro sen perder mensaxes da conversa.
+* XEP-0237: Roster Versioning fundamentalmente para aforrar datos en conexións móbiles
+* XEP-0313: Message Archive Management sincroniza o historial de mensaxes co servidor. Para obter as mensaxes recibidas cando Conversations non teña conexión.
+* XEP-0352: Client State Indication permítelle ao servidor saber se Conversations está a funcionar en segundo plano. Permítelle ao servidor aforrar ancho de banda retendo paquetes de datos de pouca importancia.
+* XEP-0363: HTTP File Upload permíteche compartir ficheiros en salas de conferencia e con contactos que non están conectados. Require un compoñente adicional no teu servidor.
diff --git a/src/conversations/fastlane/metadata/android/gl-ES/short_description.txt b/src/conversations/fastlane/metadata/android/gl-ES/short_description.txt
new file mode 100644
index 000000000..79c77166e
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/gl-ES/short_description.txt
@@ -0,0 +1 @@
+Mensaxería instantánea XMPP cifrada e fácil de usar para o teu dispositivo móbil
diff --git a/src/conversations/fastlane/metadata/android/it-IT/full_description.txt b/src/conversations/fastlane/metadata/android/it-IT/full_description.txt
new file mode 100644
index 000000000..d390af661
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/it-IT/full_description.txt
@@ -0,0 +1,39 @@
+Facile da usare, affidabile, leggero sulla batteria. Con supporto integrato per immagini, chat di gruppo e crittografia e2e.
+
+Principi di design:
+
+* Essere il più bello e facile da usare possibile senza sacrificare la sicurezza o la privacy
+* Affidarsi a protocolli esistenti ben affermati
+* Non richiedere un account Google o nello specifico Google Cloud Messaging (GCM)
+* Richiedere il minor numero di autorizzazioni possibile
+
+Caratteristiche:
+
+* Crittografia end-to-end con OMEMO o OpenPGP
+* Invio e ricezione di immagini
+* Chiamate audio e video crittografate (DTLS-SRTP)
+* Interfaccia utente intuitiva che segue le linee guida del design di Android
+* Immagini / Avatar per i tuoi contatti
+* Sincronizzazione con client desktop
+* Conferenze (con supporto ai segnalibri)
+* Integrazione della rubrica
+* Profili multipli / messaggi unificati
+* Consumo molto basso della batteria
+
+Conversations rende veramente facile creare un profilo sul server gratuito conversations.im. Tuttavia Conversations funzionerà anche con qualsiasi altro server XMPP. Molti server XMPP vengono gestiti da volontari e sono gratuiti.
+
+Caratteristiche di XMPP:
+
+Conversations funziona con tutti i server XMPP. Tuttavia XMPP è un protocollo estensibile. Anche queste estensioni sono standardizzate, con il nome XEP. Conversations supporta alcune di esse per rendere migliore l'esperienza utente. È possibile che il server XMPP che stai usando non supporti queste estensioni. Perciò, per ottenere il meglio da Conversations dovresti considerare di passare ad un server XMPP che le supporta o, ancora meglio, installarne uno tuo per te e i tuoi amici.
+
+Queste XEP sono, ad oggi:
+
+* XEP-0065: SOCKS5 Bytestreams (o mod_proxy65). Usata per trasferire file se entrambe le parti sono dietro un firewall (NAT).
+* XEP-0163: Personal Eventing Protocol. Per gli avatar.
+* XEP-0191: Blocking command. Ti consente di bloccare lo spam o i contatti senza rimuoverli dal tuo elenco.
+* XEP-0198: Stream Management. Consente a XMPP di resistere a brevi disconnessioni e cambi della connessione TCP sottostante.
+* XEP-0280: Message Carbons. Sincronizza automaticamente i messaggi che invii al client desktop, quindi ti consente di passare senza problemi dal mobile al desktop e viceversa con un'unica conversazione.
+* XEP-0237: Roster Versioning. Principalmente per risparmiare banda di rete in connessioni mobili deboli
+* XEP-0313: Message Archive Management. Sincronizza la cronologia dei messaggi con il server. Recupera i messaggi che sono stati inviati mentre Conversations era offline.
+* XEP-0352: Client State Indication. Fa sapere al server se Conversations è in secondo piano o no. Permette al server di risparmiare banda di rete trattenendo i pacchetti non importanti.
+* XEP-0363: HTTP File Upload. Ti consente di condividere file nelle conferenze e con i contatti offline. Richiede un componente aggiuntivo sul tuo server.
diff --git a/src/conversations/fastlane/metadata/android/it-IT/short_description.txt b/src/conversations/fastlane/metadata/android/it-IT/short_description.txt
new file mode 100644
index 000000000..fd4dfa96d
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/it-IT/short_description.txt
@@ -0,0 +1 @@
+Client di messaggistica XMPP facile e criptato, per il tuo dispositivo mobile
diff --git a/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt b/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt
new file mode 100644
index 000000000..ade292722
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/ja-JP/short_description.txt
@@ -0,0 +1 @@
+携帯端末で簡単に操作できるXMPP暗号化インスタント・メッセンジャー
diff --git a/src/conversations/fastlane/metadata/android/pl-PL/full_description.txt b/src/conversations/fastlane/metadata/android/pl-PL/full_description.txt
new file mode 100644
index 000000000..442e1e826
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/pl-PL/full_description.txt
@@ -0,0 +1,39 @@
+Łatwy w użyciu, godny zaufania, przyjazny dla baterii. Wbudowane wsparcie dla obrazków, rozmów grupowych i szyfrowania od nadawcy do odbiorcy.
+
+Zasady projektu:
+
+* ma być tak ładny i prosty w użyciu jak to możliwe bez uszczerbku na bezpieczeństwie lub prywatności;
+* używa istniejących, dobrze znanych protokołów;
+* nie wymaga Konta Google ani, w szczególności, Google Cloud Messaging (GCM);
+* wymaga tylko naprawdę koniecznych uprawnień.
+
+Funkcjonalność:
+
+* szyfrowanie od nadawcy do odbiorcy (E2EE) z użyciem OMEMO lub OpenPGP;
+* wysyłanie i odbieranie obrazków;
+* szyfrowane rozmowy głosowe i wideo;
+* intuicyjny interfejs użytkownika, zgodny z wytycznymi Android Design;
+* obrazki/awatary dla Twoich kontaktów;
+* synchronizacja z klientem desktopowym;
+* konferencje (z obsługą zakładek);
+* integracja z książką adresową;
+* wiele kont, zintegrowana skrzynka odbiorcza;
+* bardzo ograniczony wpływ na zużycie baterii.
+
+Conversations bardzo ułatwia rejestrację konta na darmowym serwerze conversations.im, jednak będzie działać również z każdym innym serwerem XMPP. Wiele serwerów jest uruchamianych przez wolontariuszy i są dostępne bez opłat.
+
+Funkcjonalność XMPP:
+
+Conversations działa z każdym dostępnym serwerem XMPP, jednak XMPP to rozszerzalny protokół. Rozszerzenia są ustandaryzowane w tak zwanych XEP. Conversations obsługuje sporo z nich, dzięki czemu można go przyjemniej używać. Jest jednak możliwość, że Twój obecny serwer nie obsługuje tych rozszerzeń. Aby wyciągnąć jak najwięcej z Conversations rozważ przeniesienie się na taki serwer, który je obsługuje, lub — jeszcze lepiej — uruchom własny serwer dla Ciebie i Twoich przyjaciół.
+
+Obecnie są obsługiwane następujące rozszerzenia:
+
+* XEP-0065: SOCKS5 Bytestreams (lub mod_proxy65). Będzie używany do przesyłania plików jeżeli obie strony znajdują się za zaporą (NAT);
+* XEP-0163: Personal Eventing Protocol dla awatarów;
+* XEP-0191: Blocking Command umożliwia ochronę przed spamerami lub blokowanie bez usuwanie ich z rostera;
+* XEP-0198: Stream Management pozwala na przetrwanie krótkich braków połączenia z siecią oraz zmian używanego połączenia TCP;
+* XEP-0280: Message Carbons automatycznie synchronizuje wysyłane wiadomości z klientem desktopowym i w ten sposób pozwala na proste używanie zarówno klienta mobilnego, jak i desktopowego, w jednej konwersacji;
+* XEP-0237: Roster Versioning, dzięki któremu można ograniczyć używanie sieci na słabych połączeniach komórkowych;
+* XEP-0313: Message Archive Management synchronizuje historię wiadomości z serwerem. Bądź na bieżąco z wiadomości wysłanymi gdy Conversations był rozłączony;
+* XEP-0352: Client State Indication informuje serwer o tym, czy Conversations działa w tle. Pozwala to na oszczędzanie łącza przez wstrzymywanie mniej ważnych komunikatów;
+* XEP-0363: HTTP File Upload umożliwia udostępnianie plików w konferencjach oraz rozłączonym kontaktom. Wymaga dodatkowego komponentu na Twoim serwerze.
diff --git a/src/conversations/fastlane/metadata/android/pl-PL/short_description.txt b/src/conversations/fastlane/metadata/android/pl-PL/short_description.txt
new file mode 100644
index 000000000..7869c1ba5
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/pl-PL/short_description.txt
@@ -0,0 +1 @@
+Szyfrowany, prosty w użyciu komunikator XMPP dla Twojego urządzenia mobilnego
diff --git a/src/conversations/fastlane/metadata/android/ro/full_description.txt b/src/conversations/fastlane/metadata/android/ro/full_description.txt
new file mode 100644
index 000000000..2d4ae419d
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/ro/full_description.txt
@@ -0,0 +1,38 @@
+Ușor de utilizat, fiabil, prietenos cu bateria. Cu suport încorporat pentru imagini, discuții de grup și criptare E2E.
+
+Principii de proiectare:
+
+* Să fie cât mai frumos și mai ușor de utilizat posibil, fără a sacrifica securitatea sau confidențialitatea.
+* Să se bazeze pe protocoale existente și bine stabilite
+* Nu necesită un cont Google sau în mod specific Google Cloud Messaging (GCM).
+* Să necesite cât mai puține permisiuni posibil
+
+Caracteristici:
+
+* Criptare de la un capăt-la-altul (E2E) cu OMEMO sau OpenPGP
+* Trimiterea și primirea de imagini
+* Apeluri audio și video criptate (DTLS-SRTP)
+* Interfață intuitivă care respectă liniile directoare Android Design
+* Imagini / Avataruri pentru contactele dvs.
+* Se sincronizează cu clientul desktop
+* Conferințe (cu suport pentru marcaje)
+* Integrare cu lista de contacte
+* Conturi multiple / căsuță de mesaje unificată
+* Impact foarte redus asupra duratei de viață a bateriei
+
+Conversations face foarte ușoară crearea unui cont pe serverul gratuit conversations.im. Cu toate acestea, Conversations va funcționa și cu orice alt server XMPP. O mulțime de servere XMPP sunt administrate de voluntari și sunt gratuite.
+
+Caracteristici XMPP:
+
+Conversations funcționează cu orice server XMPP existent. Cu toate acestea, XMPP este un protocol extensibil. Aceste extensii sunt, de asemenea, standardizate în așa-numitele XEP-uri. Conversations suportă câteva dintre acestea pentru a îmbunătăți experiența generală a utilizatorului. Există o șansă ca serverul XMPP actual să nu suporte aceste extensii. Prin urmare, pentru a profita la maximum de Conversations, ar trebui să luați în considerare fie trecerea la un server XMPP care să suporte aceste extensii, fie - și mai bine - să rulați propriul server XMPP pentru dumneavoastră și prietenii dumneavoastră.
+
+Aceste XEP-uri sunt - deocamdată:
+* XEP-0065: SOCKS5 Bytestreams (sau mod_proxy65). Va fi utilizat pentru a transfera fișiere dacă ambele părți se află în spatele unui firewall (NAT).
+* XEP-0163: Protocol de evenimente personale pentru avatare.
+* XEP-0191: Comanda de blocare vă permite să puneți pe lista neagră spamerii sau să blocați contactele fără a le elimina din listă.
+* XEP-0198: Stream Management permite XMPP să supraviețuiască unor mici întreruperi de rețea și schimbărilor conexiunii TCP de bază.
+* XEP-0280: Message Carbons, care sincronizează automat mesajele pe care le trimiteți în clientul desktop și vă permite astfel să treceți fără probleme de la clientul mobil la clientul desktop și înapoi în cadrul unei singure conversații.
+* XEP-0237: Roster Versioning în principal pentru a economisi lățimea de bandă în cazul conexiunilor mobile slabe
+* XEP-0313: Gestionarea arhivei de mesaje sincronizează istoricul mesajelor cu serverul. Recuperați mesajele care au fost trimise în timp ce Conversations era deconectat.
+* XEP-0352: Client State Indication permite serverului să știe dacă Conversations este sau nu în fundal. Permite serverului să economisească lățimea de bandă prin reținerea pachetelor neimportante.
+* XEP-0363: HTTP File Upload vă permite să partajați fișiere în cadrul conferințelor și cu contactele deconectate. Necesită o componentă suplimentară pe serverul dumneavoastră.
diff --git a/src/conversations/fastlane/metadata/android/ro/short_description.txt b/src/conversations/fastlane/metadata/android/ro/short_description.txt
new file mode 100644
index 000000000..143f7cb55
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/ro/short_description.txt
@@ -0,0 +1 @@
+Client de mesagerie XMPP ușor de folosit, criptat, și optimizat pentru mobile
diff --git a/src/conversations/fastlane/metadata/android/ru-RU/full_description.txt b/src/conversations/fastlane/metadata/android/ru-RU/full_description.txt
new file mode 100644
index 000000000..77c8edbb9
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/ru-RU/full_description.txt
@@ -0,0 +1,39 @@
+Простой в использовании, надежный, экономный для батареи. Со встроенной поддержкой изображений, групповых чатов и сквозным шифрованием.
+
+Принципы дизайна:
+
+* Быть максимально красивым и простым в использовании, не жертвуя при этом безопасностью и конфиденциальностью.
+* Полагаться на существующие, хорошо зарекомендовавшие себя протоколы.
+* Не требовать учетную запись Google или, в частности, Google Cloud Messaging (GCM).
+* Требовать как можно меньше разрешений.
+
+Функции:
+
+* Сквозное шифрование с помощью OMEMO или OpenPGP.
+* Отправка и получение изображений
+* Зашифрованные аудио и видео звонки (DTLS-SRTP)
+* Интуитивно понятный пользовательский интерфейс, соответствующий Рекомендациям по дизайну Android
+* Картинки / аватары для ваших контактов
+* Синхронизация с настольным клиентом
+* Конференции (с поддержкой закладок)
+* Интеграция с адресной книгой
+* Несколько учетных записей / единый почтовый ящик
+* Очень низкое влияние на время работы от батареи
+
+Conversations позволяет очень легко создать учетную запись на бесплатном сервере conversations.im. Однако Conversations также будет работать с любым другим сервером XMPP. Многие XMPP серверы управляются добровольцами и предоставляются бесплатно.
+
+Возможности XMPP:
+
+Conversations работает со всеми существующими XMPP-серверами. Однако XMPP - это расширяемый протокол. Эти расширения также стандартизированы в так называемых XEP-протоколах. Conversations поддерживает несколько из них, чтобы улучшить общее взаимодействие с пользователем. Есть вероятность, что ваш текущий XMPP-сервер не поддерживает какие либо расширения. Поэтому, чтобы получить максимальную отдачу от Conversations, вам следует либо перейти на XMPP-сервер который поддерживает эти расширения, либо, что еще лучше, запустить свой собственный XMPP-сервер для вас и ваших друзей.
+
+На данный момент эти XEP:
+
+* XEP-0065: SOCKS5 Bytestreams SOCKS5 (или mod_proxy65): будет использоваться для передачи файлов если обе стороны находятся за брандмауэром (или NAT).
+* XEP-0163: Personal Eventing Protocol: для аватаров.
+* XEP-0191: Blocking command: позволяет вносить спамеров в черный список или блокировать контакты, не удаляя их из своего списка.
+* XEP-0198: Stream Management: позволяет XMPP справляться с небольшими перебоями в сети и изменениями нижележащего TCP-соединения.
+* XEP-0280: Message Carbons: автоматически синхронизирует отправляемые вами сообщения с вашим клиентом на настольном компьютере и таким образом позволяет вам плавно переключаться с мобильного клиента на настольный клиент и обратно в рамках одного разговора.
+* XEP-0237: Roster Versioning: в основном, для экономии полосы пропускания при плохом мобильном соединении.
+* XEP-0313: Message Archive Management: синхронизирует историю сообщений с сервером. Отслеживание сообщений, отправленных во время разговоров в автономном режиме.
+* XEP-0352: Client State Indication: позволяет серверу узнать ведутся ли разговоры в фоновом режиме. Позволяет серверу экономить пропускную способность, удерживая неважные пакеты.
+* XEP-0363: HTTP File Upload: позволяет обмениваться файлами в конференциях и с контактами в автономном режиме. Требует дополнительный компонент на вашем сервере.
diff --git a/src/conversations/fastlane/metadata/android/ru-RU/short_description.txt b/src/conversations/fastlane/metadata/android/ru-RU/short_description.txt
new file mode 100644
index 000000000..059ec9eb7
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/ru-RU/short_description.txt
@@ -0,0 +1 @@
+Зашифрованный и простой в использовании XMPP мессенджер для вашего мобильного
diff --git a/src/conversations/fastlane/metadata/android/sq/full_description.txt b/src/conversations/fastlane/metadata/android/sq/full_description.txt
new file mode 100644
index 000000000..f6f03b151
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/sq/full_description.txt
@@ -0,0 +1,39 @@
+I kollajtë për t’u përdorur, i qëndrueshëm, dashamirës ndaj baterisë. Me mbulim së brendshmi për figura, fjalosje në grup dhe fshehtëzim e2e.
+
+Parime konceptuale:
+
+* Të qenët aq i bukur dhe i lehtë për përdorim sa mundet, pa sakrifikuar sigurinë ose privatësinë
+* Bazim në protokolle ekzistues, të mirënjohur
+* Mospasje nevojë për një Google Account, ose, posaçërisht Google Cloud Messaging (GCM)
+* Kërkim i sa më pak lejesh që të jetë e mundur
+
+Veçori:
+
+* Fshehtëzim skaj-më-skaj me OMEMO, ose OpenPGP
+* Dërgim dhe marrje mesazhesh
+* Thirrje të fshehtëzuara audio dhe video (DTLS-SRTP)
+* UI intuitive që ndjek udhëzimet Android Design
+* Foto / Avatarë të Kontakteve tuaja
+* Njëkohësim me klient desktop
+* Konferenca (me mbulim për faqerojtës)
+* Integrim libri adresash
+* Llogari të shumta / kuti e unifikuar të marrësh
+* Ndikim shumë i pakët në jetëgjatësinë e baterisë
+
+Conversations e bën shumë të lehtë krijimin e një llogarie te shërbyesi falas conversations.im. Megjithatë, Conversations do të funksionojë me çfarëdo shërbyesi tjetër XMPP. Plot shërbyes XMPP mbahen në punë nga vullnetarë dhe janë pa pagesë
+
+Veçori të XMPP-së:
+
+Conversations funksionon me çdo shërbyes XMPP në qarkullim. Megjithatë, XMPP është një protokoll i zgjerueshëm. Edhe këto zgjerime janë të standardizuara në të ashtuquajturit XEP-e. Conversations mbulon një a dy prej tyre, për ta bërë punën e përdoruesit më të mirë në përgjithësi. Ka një mundësi që shërbyesi juaj aktual XMPP të mos i mbulojë këto zgjerime. Ndaj, që të përfitoni maksimumin nga Conversations, duhet të shihni mundësi ose të kaloni te një shërbyes XMPP që i mbulon, ose - akoma më mirë - të vini në punë shërbyesin tuaj XMPP për ju dhe shokët tuaj.
+
+Këto XEP-e janë - deri sot:
+
+* XEP-0065: SOCKS5 Bytestreams (ose mod_proxy65). Do të përdoret për të shpërngulur kartela, nëse të dy palët gjenden pas një firewall-i (NAT).
+* XEP-0163: Personal Eventing Protocol, për avatarë
+* XEP-0191: Urdhri i bllokimeve ju lejon të kaloni në listë bllokimesh llogari që dërgojnë mesazhe të padëshiruar, ose të bllokoni kontakte pa i hequr nga lista juaj.
+* XEP-0198: Stream Management i lejon XMPP-së të mbijetojë ndërprerje të vockla rrjeti dhe ndryshime te lidhja përkatëse TCP.
+* XEP-0280: Message Carbons do të njëkohësojë automatikisht te klienti juaj desktop mesazhet që dërgoni dhe, pra, ju lejon të kaloni pa një cen nga klienti juaj për celular në atë për desktop dhe anasjelltas, brenda një bisede.
+* XEP-0237: Roster Versioning kryesisht për të kursyer sasi trafiku në lidhje celulare të dobëta
+* XEP-0313: Message Archive Management njëkohëson historik mesazhesh me shërbyesin. Ndiqni mesazhet që qenë dërguar ndërkohë që Conversations s’qe në linjë.
+* XEP-0352: Client State Indication i lejon shërbyesit të dijë nëse është apo jo në prapaskenë Conversations. I lejon shërbyesit të kursejë sasi trafiku, duke mbajtur paketa pa rëndësi.
+* XEP-0363: HTTP File Upload ju lejon të ndani me të tjerë kartela në konferenca dhe me kontakte jo në linjë. Lyp një përbërë shtesë në shërbyesin tuaj.
diff --git a/src/conversations/fastlane/metadata/android/sq/short_description.txt b/src/conversations/fastlane/metadata/android/sq/short_description.txt
new file mode 100644
index 000000000..bec029626
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/sq/short_description.txt
@@ -0,0 +1 @@
+Shkëmbyes XMPP mesazhesh aty për aty, i fshehtëzuar, i kollajtë, për celular
diff --git a/src/conversations/fastlane/metadata/android/sv-SE/full_description.txt b/src/conversations/fastlane/metadata/android/sv-SE/full_description.txt
new file mode 100644
index 000000000..c02bd4912
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/sv-SE/full_description.txt
@@ -0,0 +1,39 @@
+Lättanvänd, pålitlig och batterivänlig. Med inbyggt stöd för bilder, gruppchatt och totalsträckskryptering (end-to-end-kryptering).
+
+Designprinciper:
+
+* Vara så snygg och lättanvänd som möjligt utan att offra säkerhet eller personlig integritet
+* Bygga på väletablerade existerande protokoll
+* Inte kräva ett Google-konto eller specifikt Google Cloud Messaging (GCM)
+* Kräva så få behörigheter som möjligt
+
+Funktioner:
+
+* Totalsträckskryptering (end-to-end-kryptering) med antingen OMEMO eller OpenPGP
+* Skicka och ta emot bilder
+* Krypterade ljud- och bildsamtal (DTLS-SRTP)
+* Intuitivt användargränssnitt som följder Androids designriktlinjer
+* Bilder eller avatarer för dina kontakter
+* Synkroniserar med din skrivbordsklient
+* Konferenser (med stöd för bokmärken)
+* Integration med adressboken
+* Stöd för flera konton, med delan inkorg
+* Väldigt liten påverkan på batteriets livstid
+
+Med Conversations kan du lätt skapa ett konto på den fria servern conversations.im. Men Conversations fungerar med vilken annan XMPP-server som helst. Många XMPP-servrar drivs av volontärer och är gratis att använda.
+
+XMPP-funktioner:
+
+Conversations fungerar med alla XMPP-servrar. Men XMPP är ett utbyggbart protokoll. Dessa tillägg är också standardiserade i så kallade XEP’s. Conversations stödjer vissa av dessa tillägg för att göra den övergripande användarupplevelsen bättre. Det kan hända att din XMPP-server inte har stöd för dessa tillägg. För att få ut det mesta av Conversations bör du överväga att antingen byta till en XMPP-server som har stöd, eller - ännu bättre - kör din egen XMPP-server för dig och dina vänner!
+
+De XEP-tillägg som stöds är:
+
+* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Används för filöverföring om båda parter är bakom en brandvägg (NAT).
+* XEP-0163: Personal Eventing Protocol för avatarer
+* XEP-0191: Blocking command låter dig svartlista spammare eller blocka kontakter utan att ta bort dem
+* XEP-0198: Stream Management låter XMPP att klara av mindre nätverksavbrott och förändringar i den underliggande TCP-anslutningen
+* XEP-0280: Message Carbons som automatiskt synkar meddelanden till din skrivbordsklient och på så viss gör det möjligt att växla sömlöst från din mobil till skrivbordsklient och tillbaka inom en och samma konversation
+* XEP-0237: Roster Versioning för att spara bandbredd vid dåliga mobilanslutningar
+* XEP-0313: Message Archive Management synkronisera meddelandehistorik med server. Läs meddelanden som sänts medan Conversations var off line.
+* XEP-0352: Client State Indication låter servern veta om Conversations är körs i bakgrunden eller inte. Det gör att servern kan spara bandbredd genom att inte skicka oviktiga paket.
+* XEP-0363: HTTP File Upload låter dig dela filer i konferenser med offline-kontakter. Det kräver ett tillägg på din server.
diff --git a/src/conversations/fastlane/metadata/android/sv-SE/short_description.txt b/src/conversations/fastlane/metadata/android/sv-SE/short_description.txt
new file mode 100644
index 000000000..0177c6fe2
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/sv-SE/short_description.txt
@@ -0,0 +1 @@
+Krypterad lättanvänd XMPP-meddelandeapp för din mobil
diff --git a/src/conversations/fastlane/metadata/android/tr-TR/full_description.txt b/src/conversations/fastlane/metadata/android/tr-TR/full_description.txt
new file mode 100644
index 000000000..889d502e0
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/tr-TR/full_description.txt
@@ -0,0 +1,39 @@
+Kullanımı kolay, güvenilir, pil ömrü dostu. Resimler, gruplar ve uçtan uca şifreleme için yerleşik destek.
+
+Tasarım ilkeleri:
+
+* Gizlilik ve güvenlikten tasarruf etmeden olabildiğince iyi görünümlü ve kolay kullanımlı olmak
+* Halihazırda var olan, köklü protokollere dayanmak
+* Bir Google hesabına, özellikle Google Bulut Mesajlaşması (GCM)'e, gerek duymamak
+* Olabildiğince az izine gerek duymak
+
+Özellikler:
+
+* OMEMO veya OpenPGP ile uçtan uca şifreleme
+* Fotoğraf gönderme ve alma
+* Şifrelenmiş görüntülü ve sesli aramalar (DTLS-SRTP)
+* Android tasarım standartlarına uyan öğrenmesi kolay arayüz
+* Kişileriniz için profil fotoğrafları / Avatarlar
+* Masaüstü uygulamasıyla senkronizasyon
+* Konferanslar (yer imi desteği ile)
+* Kişiler listesiyle entegrasyon
+* Birden fazla hesap / Birleşik gelen kutusu
+* Pil ömrüne çok düşük etki
+
+Conversations, kolayca ve ücretsiz olarak conversations.im sunucusunda hesap oluşturmanıza olanak tanır. Conversations başka herhangi bir XMPP sunucusuyla da çalışır. Çoğu XMPP sunucusu gönüllüler tarafından işletilir ve ücretsizdir.
+
+XMPP Özellikleri:
+
+Conversations var olan bütün XMPP sunucularıyla kullanılabilir. Ancak XMPP, eklentiler ile genişletilebilen bir protokoldür. Bu eklentiler XEP'ler olarak standardize edilmiştir. Conversations kullanıcı deneyimini iyileştirmek için bu eklentilerden birkaçını destekler. Kullandığınız XMPP sunucusu bu eklentileri desteklemiyor olabilir. Bu yüzden Conversations'tan en iyi şekilde faydalanmak için bu eklentileri destekleyen bir sunucuya geçmeli veya, daha da iyisi, siz ve arkadaşlarınız için kendi XMPP sunucunuzu kurmalısınız.
+
+Şimdilik bu XEP'ler:
+
+* XEP-0065: SOCKS5 Bytestreams (mod_proxy65). İki taraf da bir güvenlik duvarı (NAT) arkasında ise dosya aktarımı için kullanılacaktır.
+* XEP-0163: Avatarlar için Kişisel Olay Protokolü (Personal Eventing Protocol)
+* XEP-0191: Engelleme komutu - Spam atanları ve kişilerinizi listenizden kaldırmadan engellemenizi sağlar.
+* XEP-0198: Akış Kontrolü (Stream Management) - XMPP'yi ve altındaki TCP bağlantısını küçük çaplı bağlantı kopmalarına karşı korur.
+* XEP-0280: Mesaj Karbonları - Mesajlarınızı masaüstü uygulamasıyla senkronize ederek cihazlarınız arasında kesintisiz geçiş yapmanızı sağlar.
+* XEP-0237: Roster Versioning (Liste Sürüm Takibi) - Zayıf mobil ağlarda bant aralığından tasarruf etmek amacıyla.
+* XEP-0313: Mesaj Arşivi Yönetimi - Çevrimdışı olduğunuzda bile mesaj almaya devam edebilmeniz için mesajlarınızı sunucuyla senkronize eder.
+* XEP-0352: İstemci Durum Bildirimi - Conversations'un arkaplanda çalıştığını sunucuya bildir. Sunucunun önemsiz paketleri saklayarak veriden tasarruf etmesini sağlar.
+* XEP-0363: HTTP Dosya Yükleme - Konferanslarla ve çevrimdışı kişilerle dosya paylaşabilmenizi sağlar. Sunucunuzda ek bileşen gerektirir.
diff --git a/src/conversations/fastlane/metadata/android/tr-TR/short_description.txt b/src/conversations/fastlane/metadata/android/tr-TR/short_description.txt
new file mode 100644
index 000000000..1eb74ef8f
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/tr-TR/short_description.txt
@@ -0,0 +1 @@
+Mobil cihazınız için şifrelenmiş, kullanımı kolay bir XMPP mesajlaşma uygulaması
diff --git a/src/conversations/fastlane/metadata/android/uk/full_description.txt b/src/conversations/fastlane/metadata/android/uk/full_description.txt
new file mode 100644
index 000000000..39971ed55
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/uk/full_description.txt
@@ -0,0 +1,39 @@
+Надійний, простий у використанні, ощадливо витрачає заряд акумулятора. Має вбудовану підтримку зображень, групових чатів і наскрізного шифрування.
+
+Принципи проєктування:
+
+* Бути максимально красивим та простим у використанні, не жертвуючи безпекою чи конфіденційністю
+* Покладатися на існуючі, добре встановлені протоколи
+* Не вимагати облікового запису Google, зокрема Google Cloud Messaging (GCM)
+* Вимагати якомога менше дозволів
+
+Функції:
+
+* Наскрізне шифрування (від відправника до одержувача) за допомогою OMEMO або OpenPGP
+* Надсилання та отримання зображень
+* Зашифровані голосові та відеодзвінки (DTLS-SRTP)
+* Інтуїтивно зрозумілий інтерфейс користувача, який відповідає вказівкам Android Design
+* Зображення / Аватари для Ваших контактів
+* Синхронізація з настільним клієнтом
+* Конференції (з підтримкою закладок)
+* Інтеграція адресної книги
+* Кілька облікових записів / єдина папка вхідних
+* Дуже низький вплив на термін служби акумулятора
+
+Conversations дозволяє легко створити обліковий запис на безкоштовному сервері conversations.im. Однак Conversations працюватиме також із будь-яким іншим XMPP-сервером. Чимало серверів XMPP обслуговуються волонтерами і є безкоштовними.
+
+Функції XMPP:
+
+Conversations працює з будь-яким сервером XMPP. Проте XMPP — розширюваний протокол. Розширення також стандартизовані в так званих XEP. Conversations підтримує кілька з них, щоб покращити загальний досвід користування. Може виявитися, що Ваш поточний сервер XMPP не підтримує цих розширень. Тому, щоб отримати максимум від Conversations, розгляньте перехід на XMPP-сервер з підтримкою цих розширень або — ще краще — запускайте власний сервер XMPP для себе і своїх друзів.
+
+На даний час підтримуються такі XEP:
+
+* XEP-0065: SOCKS5 Bytestreams (або mod_proxy65). Використовується для передачі файлів, якщо обидві сторони знаходяться за брандмауером (NAT).
+* XEP-0163: персональний протокол подій для аватарів
+* XEP-0191: команда блокування дозволяє Вам заносити спамерів у чорний список або блокувати контакти, не видаляючи їх зі свого списку.
+* XEP-0198: керування потоками дозволяє XMPP витримувати невеликі перебої в мережі та зміни основного TCP-з'єднання.
+* XEP-0280: Message Carbons, який автоматично синхронізує повідомлення, які Ви надсилаєте, на настільний клієнт і, таким чином, дозволяє плавно переключатися з мобільного клієнта на клієнт для настільного ПК і назад протягом однієї розмови.
+* XEP-0237: версія списку в основному для економії пропускної здатності при поганих мобільних з'єднаннях
+* XEP-0313: керування архівом повідомлень синхронізує історію повідомлень із сервером. Дізнавайтеся про повідомлення, надіслані, поки Conversations був офлайн.
+* XEP-0352: індикація стану клієнта повідомляє серверу, чи працює Conversations у фоновому режимі. Дозволяє серверу заощаджувати пропускну здатність, утримуючи неважливі пакети.
+* XEP-0363: завантаження файлів HTTP дозволяє обмінюватися файлами в конференціях і з офлайн-контактами. Потрібен додатковий компонент на Вашому сервері.
diff --git a/src/conversations/fastlane/metadata/android/uk/short_description.txt b/src/conversations/fastlane/metadata/android/uk/short_description.txt
new file mode 100644
index 000000000..300b89277
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/uk/short_description.txt
@@ -0,0 +1 @@
+Простий у використанні XMPP-клієнт з підтримкою шифрування для Вашого телефона
diff --git a/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt b/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt
new file mode 100644
index 000000000..87d4ecea0
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/zh-CN/full_description.txt
@@ -0,0 +1,39 @@
+易用、可靠、省电。内置支持图片、群聊和端到端加密功能。
+
+设计原则:
+
+* 在不牺牲安全性和隐私性的前提下,尽可能美观易用
+* 依赖现有的、完善的协议
+* 不需要 Google 账号或特定的 Google 云通讯服务(GCM)
+* 要求尽可能少的权限
+
+特点:
+
+* 使用 OMEMO 或 OpenPGP 进行端到端加密
+* 发送和接收图片
+* 加密音视频通话(DTLS-SRTP)
+* 直观的用户界面,遵循 Android 设计准则
+* 为您的联系人添加图片/头像
+* 与桌面客户端同步
+* 群聊(支持书签功能)
+* 通讯录集成
+* 多账号/统一消息栏
+* 对电池寿命的影响非常小
+
+Conversations 使在免费的 conversations.im 服务器上创建账号变得非常简单。不过,Conversations 也适用于任何其他 XMPP 服务器。许多 XMPP 服务器都是由志愿者免费运行的。
+
+XMPP 功能:
+
+Conversations 适用于所有 XMPP 服务器。然而,XMPP 是一种可扩展的协议。这些扩展在所谓的 XMPP 扩展协议中也是标准化的。Conversations 支持其中的一些扩展,以使整体用户体验更好。有一种可能是您当前的 XMPP 服务器不支持这些扩展。因此,要想充分使用 Conversations 的功能,您应该考虑切换到支持这些扩展的 XMPP 服务器,甚至有更好的方式,或者为您和您的朋友运行自己的 XMPP 服务器。
+
+到目前为止,这些 XMPP 扩展协议是:
+
+* XEP-0065:SOCKS5 字节流(或 mod_proxy65)。如果双方都在防火墙(NAT)后面,将用于传输文件。
+* XEP-0163:个人事件协议(头像)
+* XEP-0191:屏蔽命令可让您将垃圾消息发送者列入黑名单或屏蔽的联系人中,而不会将其从花名册中删除。
+* XEP-0198:流管理允许 XMPP 在小规模网络中断和底层 TCP 连接发生变化时继续运行。
+* XEP-0280:消息抄送,可自动将您发送的消息同步到桌面客户端,因此您可以在一次对话中从手机客户端无缝切换到桌面客户端,然后再返回。
+* XEP-0237:花名册版本控制主要是为了在移动连接不佳的情况下节省带宽
+* XEP-0313:消息存档管理与服务器同步消息历史记录。补发 Conversations 离线时发送的消息。
+* XEP-0352:客户端状态指示让服务器知道 Conversations 是否在后台。允许服务器保留不重要的数据包,从而节省带宽。
+* XEP-0363:通过 HTTP 文件上传功能,您可以在群聊中与离线联系人分享文件。需要在服务器上安装额外组件。
diff --git a/src/conversations/fastlane/metadata/android/zh-CN/short_description.txt b/src/conversations/fastlane/metadata/android/zh-CN/short_description.txt
new file mode 100644
index 000000000..40275beb3
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/zh-CN/short_description.txt
@@ -0,0 +1 @@
+为您的移动设备提供加密、易用的 XMPP 即时通讯软件
diff --git a/src/conversations/fastlane/metadata/android/zh-TW/full_description.txt b/src/conversations/fastlane/metadata/android/zh-TW/full_description.txt
new file mode 100644
index 000000000..0e9114c98
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/zh-TW/full_description.txt
@@ -0,0 +1,39 @@
+易於使用、可靠、省電,且帶有內建圖像支援、群組聊天和端對端加密的 XMPP 用戶端。
+
+設計原則:
+
+* 在不犧牲安全或隱私權的前提下,盡可能地保持美觀性和易用性
+* 仰賴現存的、已建立的通訊協定
+* 不需要 Google 帳戶或特別的 Google 雲端訊息 (GCM)
+* 需要少量可能的權限
+
+功能:
+
+* OMEMO 或 OpenPGP 端對端加密
+* 傳送並接收圖像
+* 加密的音訊和視訊通話 (DTLS-SRTP)
+* 依循 Android 設計指南的直覺化 UI
+* 為您的聯絡人顯示圖片/ 頭像
+* 與桌面用戶端同步
+* 會議 (書籤支援)
+* 通訊錄整合
+* 多個帳戶/整合收件匣
+* 對電池壽命的極低影響
+
+Conversations 使在免費的 conversations.im 伺服器上建立一個帳戶變得極為輕易。然而 Conversations 也可在其他 XMPP 伺服器上運作,很多 XMPP 伺服器是由志工驅動的,並且完全免費。
+
+XMPP 功能:
+
+Conversations 可以在所有 XMPP 伺服器上運作。然而,XMPP 是一個可以擴充的通訊協定,這些擴充功能在所謂的 XEP 中也是標準化的。Conversations 支援其中的幾個,已使使用者體驗更佳。有可能您目前的 XMPP 伺服器並不支援這些擴充功能,因此,為了最大限度的發揮 Conversations 的作用,您應該考慮切換到一個支援這些擴充功能的 XMPP 伺服器,或者甚至更好——為您和您的朋友驅動您自己的 XMPP 伺服器。
+
+如下 XEP - 截止目前:
+
+* XEP-0065:SOCKS5 位元資料流 (或 mod_proxy65),將被用於傳輸檔案,如果雙方都在防火牆之後 (NAT)。
+* XEP-0163:用於虛擬化身的私人活動通訊協定
+* XEP-0191:封鎖命令可讓您將濫發垃圾郵件者列入黑名單,或封鎖聯絡人而不把他們從名冊中移除。
+* XEP-0198:串流管理允許 XMPP 在小型網路中斷和基礎 TCP 連線的變更中生存。
+* XEP-0280:訊息副本,自動將您傳送的訊息同步至桌面用戶端,從而允許您在一次會話中從您的行動用戶端無縫切換到您的桌面用戶端。
+* XEP-0237:名冊版本管理,主要是為了節省行動連線不佳時的頻寬。
+* XEP-0313:訊息封存管理將訊息記錄與伺服器同步,隨時掌握離線傳送的訊息。
+* XEP-0352:用戶端狀態指示可讓伺服器知道 Conversations 是否在背景,允許伺服器透過扣留不必要的封裝來節省頻寬。
+* XEP-0363:HTTP 檔案上傳允許您在會議中或與離線聯絡人分享檔案,需要在您的伺服器上有一個額外的元件。
diff --git a/src/conversations/fastlane/metadata/android/zh-TW/short_description.txt b/src/conversations/fastlane/metadata/android/zh-TW/short_description.txt
new file mode 100644
index 000000000..06e14ddbe
--- /dev/null
+++ b/src/conversations/fastlane/metadata/android/zh-TW/short_description.txt
@@ -0,0 +1 @@
+可加密、易於使用的 XMPP 即時訊息,為您的行動裝置設計
diff --git a/src/main/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java
similarity index 100%
rename from src/main/java/eu/siacs/conversations/entities/AccountConfiguration.java
rename to src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java
diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
new file mode 100644
index 000000000..dd96468ee
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
@@ -0,0 +1,508 @@
+package eu.siacs.conversations.services;
+
+import static eu.siacs.conversations.utils.Compatibility.s;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.provider.OpenableColumns;
+import android.util.Log;
+
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Stopwatch;
+import com.google.common.io.CountingInputStream;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.DatabaseBackend;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.ui.ManageAccountActivity;
+import eu.siacs.conversations.utils.BackupFileHeader;
+import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
+import eu.siacs.conversations.worker.ExportBackupWorker;
+import eu.siacs.conversations.xmpp.Jid;
+
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.io.CipherInputStream;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipException;
+
+import javax.crypto.BadPaddingException;
+
+public class ImportBackupService extends Service {
+
+ private static final ExecutorService BACKUP_FILE_READER_EXECUTOR =
+ Executors.newSingleThreadExecutor();
+
+ private static final int NOTIFICATION_ID = 21;
+ private static final AtomicBoolean running = new AtomicBoolean(false);
+ private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
+ private final SerialSingleThreadExecutor executor =
+ new SerialSingleThreadExecutor(getClass().getSimpleName());
+ private final Set mOnBackupProcessedListeners =
+ Collections.newSetFromMap(new WeakHashMap<>());
+ private DatabaseBackend mDatabaseBackend;
+ private NotificationManager notificationManager;
+
+ private static final Collection TABLE_ALLOW_LIST =
+ Arrays.asList(
+ Account.TABLENAME,
+ Conversation.TABLENAME,
+ Message.TABLENAME,
+ SQLiteAxolotlStore.PREKEY_TABLENAME,
+ SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.IDENTITIES_TABLENAME);
+ private static final Pattern COLUMN_PATTERN = Pattern.compile("^[a-zA-Z_]+$");
+
+ @Override
+ public void onCreate() {
+ mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
+ notificationManager =
+ (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent == null) {
+ return START_NOT_STICKY;
+ }
+ final String password = intent.getStringExtra("password");
+ final Uri data = intent.getData();
+ final Uri uri;
+ if (data == null) {
+ final String file = intent.getStringExtra("file");
+ uri = file == null ? null : Uri.fromFile(new File(file));
+ } else {
+ uri = data;
+ }
+
+ if (password == null || password.isEmpty() || uri == null) {
+ return START_NOT_STICKY;
+ }
+ if (running.compareAndSet(false, true)) {
+ executor.execute(
+ () -> {
+ startForegroundService();
+ final boolean success = importBackup(uri, password);
+ stopForeground(true);
+ running.set(false);
+ if (success) {
+ notifySuccess();
+ }
+ stopSelf();
+ });
+ } else {
+ Log.d(Config.LOGTAG, "backup already running");
+ }
+ return START_NOT_STICKY;
+ }
+
+ public boolean getLoadingState() {
+ return running.get();
+ }
+
+ public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) {
+ executor.execute(
+ () -> {
+ final List accounts = mDatabaseBackend.getAccountJids(false);
+ final ArrayList backupFiles = new ArrayList<>();
+ final Set apps =
+ new HashSet<>(
+ Arrays.asList(
+ "Conversations",
+ "Quicksy",
+ getString(R.string.app_name)));
+ final List directories = new ArrayList<>();
+ for (final String app : apps) {
+ directories.add(FileBackend.getLegacyBackupDirectory(app));
+ }
+ directories.add(FileBackend.getBackupDirectory(this));
+ for (final File directory : directories) {
+ if (!directory.exists() || !directory.isDirectory()) {
+ Log.d(
+ Config.LOGTAG,
+ "directory not found: " + directory.getAbsolutePath());
+ continue;
+ }
+ final File[] files = directory.listFiles();
+ if (files == null) {
+ continue;
+ }
+ Log.d(Config.LOGTAG, "looking for backups in " + directory);
+ for (final File file : files) {
+ if (file.isFile() && file.getName().endsWith(".ceb")) {
+ try {
+ final BackupFile backupFile = BackupFile.read(file);
+ if (accounts.contains(backupFile.getHeader().getJid())) {
+ Log.d(
+ Config.LOGTAG,
+ "skipping backup for "
+ + backupFile.getHeader().getJid());
+ } else {
+ backupFiles.add(backupFile);
+ }
+ } catch (final IOException
+ | IllegalArgumentException
+ | BackupFileHeader.OutdatedBackupFileVersion e) {
+ Log.d(Config.LOGTAG, "unable to read backup file ", e);
+ }
+ }
+ }
+ }
+ Collections.sort(
+ backupFiles, Comparator.comparing(a -> a.header.getJid().toString()));
+ onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
+ });
+ }
+
+ private void startForegroundService() {
+ startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0));
+ }
+
+ private void updateImportBackupNotification(final long total, final long current) {
+ final int max;
+ final int progress;
+ if (total == 0) {
+ max = 1;
+ progress = 0;
+ } else {
+ max = 100;
+ progress = (int) (current * 100 / total);
+ }
+ final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ try {
+ notificationManager.notify(
+ NOTIFICATION_ID, createImportBackupNotification(max, progress));
+ } catch (final RuntimeException e) {
+ Log.d(Config.LOGTAG, "unable to make notification", e);
+ }
+ }
+
+ private Notification createImportBackupNotification(final int max, final int progress) {
+ NotificationCompat.Builder mBuilder =
+ new NotificationCompat.Builder(getBaseContext(), "backup");
+ mBuilder.setContentTitle(getString(R.string.restoring_backup))
+ .setSmallIcon(R.drawable.ic_unarchive_24dp)
+ .setProgress(max, progress, max == 1 && progress == 0);
+ return mBuilder.build();
+ }
+
+ private boolean importBackup(final Uri uri, final String password) {
+ Log.d(Config.LOGTAG, "importing backup from " + uri);
+ final Stopwatch stopwatch = Stopwatch.createStarted();
+ try {
+ final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
+ final InputStream inputStream;
+ final String path = uri.getPath();
+ final long fileSize;
+ if ("file".equals(uri.getScheme()) && path != null) {
+ final File file = new File(path);
+ inputStream = new FileInputStream(file);
+ fileSize = file.length();
+ } else {
+ final Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
+ if (returnCursor == null) {
+ fileSize = 0;
+ } else {
+ returnCursor.moveToFirst();
+ fileSize =
+ returnCursor.getLong(
+ returnCursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
+ returnCursor.close();
+ }
+ inputStream = getContentResolver().openInputStream(uri);
+ }
+ if (inputStream == null) {
+ synchronized (mOnBackupProcessedListeners) {
+ for (final OnBackupProcessed l : mOnBackupProcessedListeners) {
+ l.onBackupRestoreFailed();
+ }
+ }
+ return false;
+ }
+ final CountingInputStream countingInputStream = new CountingInputStream(inputStream);
+ final DataInputStream dataInputStream = new DataInputStream(countingInputStream);
+ final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
+ Log.d(Config.LOGTAG, backupFileHeader.toString());
+
+ if (mDatabaseBackend.getAccountJids(false).contains(backupFileHeader.getJid())) {
+ synchronized (mOnBackupProcessedListeners) {
+ for (OnBackupProcessed l : mOnBackupProcessedListeners) {
+ l.onAccountAlreadySetup();
+ }
+ }
+ return false;
+ }
+
+ final byte[] key = ExportBackupWorker.getKey(password, backupFileHeader.getSalt());
+
+ final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
+ cipher.init(
+ false,
+ new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv()));
+ final CipherInputStream cipherInputStream =
+ new CipherInputStream(countingInputStream, cipher);
+
+ final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
+ final BufferedReader reader =
+ new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8));
+ final JsonReader jsonReader = new JsonReader(reader);
+ if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
+ jsonReader.beginArray();
+ } else {
+ throw new IllegalStateException("Backup file did not begin with array");
+ }
+ db.beginTransaction();
+ while (jsonReader.hasNext()) {
+ if (jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
+ importRow(db, jsonReader, backupFileHeader.getJid(), password);
+ } else if (jsonReader.peek() == JsonToken.END_ARRAY) {
+ jsonReader.endArray();
+ continue;
+ }
+ updateImportBackupNotification(fileSize, countingInputStream.getCount());
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ final Jid jid = backupFileHeader.getJid();
+ final Cursor countCursor =
+ db.rawQuery(
+ "select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?",
+ new String[] {
+ jid.getEscapedLocal(), jid.getDomain().toEscapedString()
+ });
+ countCursor.moveToFirst();
+ final int count = countCursor.getInt(0);
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "restored %d messages in %s", count, stopwatch.stop().toString()));
+ countCursor.close();
+ stopBackgroundService();
+ synchronized (mOnBackupProcessedListeners) {
+ for (OnBackupProcessed l : mOnBackupProcessedListeners) {
+ l.onBackupRestored();
+ }
+ }
+ return true;
+ } catch (final Exception e) {
+ final Throwable throwable = e.getCause();
+ final boolean reasonWasCrypto =
+ throwable instanceof BadPaddingException || e instanceof ZipException;
+ synchronized (mOnBackupProcessedListeners) {
+ for (OnBackupProcessed l : mOnBackupProcessedListeners) {
+ if (reasonWasCrypto) {
+ l.onBackupDecryptionFailed();
+ } else {
+ l.onBackupRestoreFailed();
+ }
+ }
+ }
+ Log.d(Config.LOGTAG, "error restoring backup " + uri, e);
+ return false;
+ }
+ }
+
+ private void importRow(
+ final SQLiteDatabase db,
+ final JsonReader jsonReader,
+ final Jid account,
+ final String passphrase)
+ throws IOException {
+ jsonReader.beginObject();
+ final String firstParameter = jsonReader.nextName();
+ if (!firstParameter.equals("table")) {
+ throw new IllegalStateException("Expected key 'table'");
+ }
+ final String table = jsonReader.nextString();
+ if (!TABLE_ALLOW_LIST.contains(table)) {
+ throw new IOException(String.format("%s is not recognized for import", table));
+ }
+ final ContentValues contentValues = new ContentValues();
+ final String secondParameter = jsonReader.nextName();
+ if (!secondParameter.equals("values")) {
+ throw new IllegalStateException("Expected key 'values'");
+ }
+ jsonReader.beginObject();
+ while (jsonReader.peek() != JsonToken.END_OBJECT) {
+ final String name = jsonReader.nextName();
+ if (COLUMN_PATTERN.matcher(name).matches()) {
+ if (jsonReader.peek() == JsonToken.NULL) {
+ jsonReader.nextNull();
+ contentValues.putNull(name);
+ } else if (jsonReader.peek() == JsonToken.NUMBER) {
+ contentValues.put(name, jsonReader.nextLong());
+ } else {
+ contentValues.put(name, jsonReader.nextString());
+ }
+ } else {
+ throw new IOException(String.format("Unexpected column name %s", name));
+ }
+ }
+ jsonReader.endObject();
+ jsonReader.endObject();
+ if (Account.TABLENAME.equals(table)) {
+ final Jid jid =
+ Jid.of(
+ contentValues.getAsString(Account.USERNAME),
+ contentValues.getAsString(Account.SERVER),
+ null);
+ final String password = contentValues.getAsString(Account.PASSWORD);
+ if (jid.equals(account) && passphrase.equals(password)) {
+ Log.d(Config.LOGTAG, "jid and password from backup header had matching row");
+ } else {
+ throw new IOException("jid or password in table did not match backup");
+ }
+ }
+ db.insert(table, null, contentValues);
+ }
+
+ private void notifySuccess() {
+ NotificationCompat.Builder mBuilder =
+ new NotificationCompat.Builder(getBaseContext(), "backup");
+ mBuilder.setContentTitle(getString(R.string.notification_restored_backup_title))
+ .setContentText(getString(R.string.notification_restored_backup_subtitle))
+ .setAutoCancel(true)
+ .setContentIntent(
+ PendingIntent.getActivity(
+ this,
+ 145,
+ new Intent(this, ManageAccountActivity.class),
+ s()
+ ? PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT))
+ .setSmallIcon(R.drawable.ic_unarchive_24dp);
+ notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
+ }
+
+ private void stopBackgroundService() {
+ Intent intent = new Intent(this, XmppConnectionService.class);
+ stopService(intent);
+ }
+
+ public void removeOnBackupProcessedListener(OnBackupProcessed listener) {
+ synchronized (mOnBackupProcessedListeners) {
+ mOnBackupProcessedListeners.remove(listener);
+ }
+ }
+
+ public void addOnBackupProcessedListener(OnBackupProcessed listener) {
+ synchronized (mOnBackupProcessedListeners) {
+ mOnBackupProcessedListeners.add(listener);
+ }
+ }
+
+ public static ListenableFuture read(final Context context, final Uri uri) {
+ return Futures.submit(() -> BackupFile.read(context, uri), BACKUP_FILE_READER_EXECUTOR);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return this.binder;
+ }
+
+ public interface OnBackupFilesLoaded {
+ void onBackupFilesLoaded(List files);
+ }
+
+ public interface OnBackupProcessed {
+ void onBackupRestored();
+
+ void onBackupDecryptionFailed();
+
+ void onBackupRestoreFailed();
+
+ void onAccountAlreadySetup();
+ }
+
+ public static class BackupFile {
+ private final Uri uri;
+ private final BackupFileHeader header;
+
+ private BackupFile(Uri uri, BackupFileHeader header) {
+ this.uri = uri;
+ this.header = header;
+ }
+
+ private static BackupFile read(File file) throws IOException {
+ final FileInputStream fileInputStream = new FileInputStream(file);
+ final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
+ BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
+ fileInputStream.close();
+ return new BackupFile(Uri.fromFile(file), backupFileHeader);
+ }
+
+ public static BackupFile read(final Context context, final Uri uri) throws IOException {
+ final InputStream inputStream = context.getContentResolver().openInputStream(uri);
+ if (inputStream == null) {
+ throw new FileNotFoundException();
+ }
+ final DataInputStream dataInputStream = new DataInputStream(inputStream);
+ final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
+ inputStream.close();
+ return new BackupFile(uri, backupFileHeader);
+ }
+
+ public BackupFileHeader getHeader() {
+ return header;
+ }
+
+ public Uri getUri() {
+ return uri;
+ }
+ }
+
+ public class ImportBackupServiceBinder extends Binder {
+ public ImportBackupService getService() {
+ return ImportBackupService.this;
+ }
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java
new file mode 100644
index 000000000..b2a0d17f4
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java
@@ -0,0 +1,38 @@
+package eu.siacs.conversations.services;
+
+import android.content.Intent;
+import android.util.Log;
+
+import eu.siacs.conversations.Config;
+
+public class QuickConversationsService extends AbstractQuickConversationsService {
+
+ QuickConversationsService(XmppConnectionService xmppConnectionService) {
+ super(xmppConnectionService);
+ }
+
+ @Override
+ public void considerSync() {
+
+ }
+
+ @Override
+ public void signalAccountStateChange() {
+
+ }
+
+ @Override
+ public boolean isSynchronizing() {
+ return false;
+ }
+
+ @Override
+ public void considerSyncBackground(boolean force) {
+
+ }
+
+ @Override
+ public void handleSmsReceived(Intent intent) {
+ Log.d(Config.LOGTAG,"ignoring received SMS");
+ }
+}
\ No newline at end of file
diff --git a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java
new file mode 100644
index 000000000..9228a5170
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java
@@ -0,0 +1,187 @@
+package eu.siacs.conversations.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.databinding.DataBindingUtil;
+
+import com.google.android.material.color.MaterialColors;
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityEasyInviteBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.BarcodeProvider;
+import eu.siacs.conversations.utils.EasyOnboardingInvite;
+import eu.siacs.conversations.xmpp.Jid;
+
+public class EasyOnboardingInviteActivity extends XmppActivity
+ implements EasyOnboardingInvite.OnInviteRequested {
+
+ private ActivityEasyInviteBinding binding;
+
+ private EasyOnboardingInvite easyOnboardingInvite;
+
+ @Override
+ public void onCreate(final Bundle bundle) {
+ super.onCreate(bundle);
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite);
+ setSupportActionBar(binding.toolbar);
+ configureActionBar(getSupportActionBar(), true);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ this.binding.shareButton.setOnClickListener(v -> share());
+ if (bundle != null && bundle.containsKey("invite")) {
+ this.easyOnboardingInvite = bundle.getParcelable("invite");
+ if (this.easyOnboardingInvite != null) {
+ showInvite(this.easyOnboardingInvite);
+ return;
+ }
+ }
+ this.showLoading();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.easy_onboarding_invite, menu);
+ final MenuItem share = menu.findItem(R.id.action_share);
+ share.setVisible(easyOnboardingInvite != null);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ if (menuItem.getItemId() == R.id.action_share) {
+ share();
+ return true;
+ } else {
+ return super.onOptionsItemSelected(menuItem);
+ }
+ }
+
+ private void share() {
+ final String shareText =
+ getString(
+ R.string.easy_invite_share_text,
+ easyOnboardingInvite.getDomain(),
+ easyOnboardingInvite.getShareableLink());
+ final Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
+ sendIntent.setType("text/plain");
+ startActivity(Intent.createChooser(sendIntent, getString(R.string.share_invite_with)));
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ invalidateOptionsMenu();
+ if (easyOnboardingInvite != null) {
+ showInvite(easyOnboardingInvite);
+ } else {
+ showLoading();
+ }
+ }
+
+ private void showLoading() {
+ this.binding.inProgress.setVisibility(View.VISIBLE);
+ this.binding.invite.setVisibility(View.GONE);
+ }
+
+ private void showInvite(final EasyOnboardingInvite invite) {
+ this.binding.inProgress.setVisibility(View.GONE);
+ this.binding.invite.setVisibility(View.VISIBLE);
+ this.binding.tapToShare.setText(
+ getString(R.string.tap_share_button_send_invite, invite.getDomain()));
+ final Point size = new Point();
+ getWindowManager().getDefaultDisplay().getSize(size);
+ final int width = Math.min(size.x, size.y);
+ final boolean nightMode =
+ (this.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ final int black;
+ final int white;
+ if (nightMode) {
+ black =
+ MaterialColors.getColor(
+ this,
+ com.google.android.material.R.attr.colorSurface,
+ "No surface color configured");
+ white =
+ MaterialColors.getColor(
+ this,
+ com.google.android.material.R.attr.colorSurfaceInverse,
+ "No inverse surface color configured");
+ } else {
+ black =
+ MaterialColors.getColor(
+ this,
+ com.google.android.material.R.attr.colorSurfaceInverse,
+ "No inverse surface color configured");
+ white =
+ MaterialColors.getColor(
+ this,
+ com.google.android.material.R.attr.colorSurface,
+ "No surface color configured");
+ }
+ final Bitmap bitmap =
+ BarcodeProvider.create2dBarcodeBitmap(
+ invite.getShareableLink(), width, black, white);
+ binding.qrCode.setImageBitmap(bitmap);
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle bundle) {
+ super.onSaveInstanceState(bundle);
+ if (easyOnboardingInvite != null) {
+ bundle.putParcelable("invite", easyOnboardingInvite);
+ }
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ if (easyOnboardingInvite != null) {
+ return;
+ }
+ final Intent launchIntent = getIntent();
+ final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT);
+ final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra);
+ if (jid == null) {
+ return;
+ }
+ final Account account = xmppConnectionService.findAccountByJid(jid);
+ xmppConnectionService.requestEasyOnboardingInvite(account, this);
+ }
+
+ public static void launch(final Account account, final Activity context) {
+ final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
+ context.startActivity(intent);
+ }
+
+ @Override
+ public void inviteRequested(EasyOnboardingInvite invite) {
+ this.easyOnboardingInvite = invite;
+ Log.d(Config.LOGTAG, "invite requested");
+ refreshUi();
+ }
+
+ @Override
+ public void inviteRequestFailed(final String message) {
+ runOnUiThread(
+ () -> {
+ if (!Strings.isNullOrEmpty(message)) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ }
+ finish();
+ });
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java
new file mode 100644
index 000000000..331857e29
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java
@@ -0,0 +1,379 @@
+package eu.siacs.conversations.ui;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+import androidx.databinding.DataBindingUtil;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.snackbar.Snackbar;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityImportBackupBinding;
+import eu.siacs.conversations.databinding.DialogEnterPasswordBinding;
+import eu.siacs.conversations.services.ImportBackupService;
+import eu.siacs.conversations.ui.adapter.BackupFileAdapter;
+import eu.siacs.conversations.ui.util.MainThreadExecutor;
+import eu.siacs.conversations.utils.BackupFileHeader;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class ImportBackupActivity extends ActionBarActivity
+ implements ServiceConnection,
+ ImportBackupService.OnBackupFilesLoaded,
+ BackupFileAdapter.OnItemClickedListener,
+ ImportBackupService.OnBackupProcessed {
+
+ private ActivityImportBackupBinding binding;
+
+ private BackupFileAdapter backupFileAdapter;
+ private ImportBackupService service;
+
+ private boolean mLoadingState = false;
+ private final ActivityResultLauncher requestPermissions =
+ registerForActivityResult(
+ new ActivityResultContracts.RequestMultiplePermissions(),
+ results -> {
+ if (results.containsValue(Boolean.TRUE)) {
+ final var service = this.service;
+ if (service == null) {
+ return;
+ }
+ service.loadBackupFiles(this);
+ }
+ });
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+ setLoadingState(
+ savedInstanceState != null
+ && savedInstanceState.getBoolean("loading_state", false));
+ this.backupFileAdapter = new BackupFileAdapter();
+ this.binding.list.setAdapter(this.backupFileAdapter);
+ this.backupFileAdapter.setOnItemClickedListener(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.import_backup, menu);
+ final MenuItem openBackup = menu.findItem(R.id.action_open_backup_file);
+ openBackup.setVisible(!this.mLoadingState);
+ return true;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ bundle.putBoolean("loading_state", this.mLoadingState);
+ super.onSaveInstanceState(bundle);
+ }
+
+ @Override
+ public void onStart() {
+
+ super.onStart();
+ bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
+ final Intent intent = getIntent();
+ if (intent != null
+ && Intent.ACTION_VIEW.equals(intent.getAction())
+ && !this.mLoadingState) {
+ Uri uri = intent.getData();
+ if (uri != null) {
+ openBackupFileFromUri(uri, true);
+ return;
+ }
+ }
+ final List desiredPermission;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ desiredPermission =
+ ImmutableList.of(
+ Manifest.permission.READ_MEDIA_IMAGES,
+ Manifest.permission.READ_MEDIA_VIDEO,
+ Manifest.permission.READ_MEDIA_AUDIO,
+ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
+ } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
+ desiredPermission =
+ ImmutableList.of(
+ Manifest.permission.READ_MEDIA_IMAGES,
+ Manifest.permission.READ_MEDIA_VIDEO,
+ Manifest.permission.READ_MEDIA_AUDIO);
+ } else {
+ desiredPermission = ImmutableList.of(Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+ final Set declaredPermission = getDeclaredPermission();
+ if (declaredPermission.containsAll(desiredPermission)) {
+ requestPermissions.launch(desiredPermission.toArray(new String[0]));
+ } else {
+ Log.d(Config.LOGTAG, "Manifest is lacking some desired permission. not requesting");
+ }
+ }
+
+ private Set getDeclaredPermission() {
+ final String[] permissions;
+ try {
+ permissions =
+ getPackageManager()
+ .getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS)
+ .requestedPermissions;
+ } catch (final PackageManager.NameNotFoundException e) {
+ return Collections.emptySet();
+ }
+ return ImmutableSet.copyOf(permissions);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (this.service != null) {
+ this.service.removeOnBackupProcessedListener(this);
+ }
+ unbindService(this);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ ImportBackupService.ImportBackupServiceBinder binder =
+ (ImportBackupService.ImportBackupServiceBinder) service;
+ this.service = binder.getService();
+ this.service.addOnBackupProcessedListener(this);
+ setLoadingState(this.service.getLoadingState());
+ this.service.loadBackupFiles(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ this.service = null;
+ }
+
+ @Override
+ public void onBackupFilesLoaded(final List files) {
+ runOnUiThread(() -> backupFileAdapter.setFiles(files));
+ }
+
+ @Override
+ public void onClick(final ImportBackupService.BackupFile backupFile) {
+ showEnterPasswordDialog(backupFile, false);
+ }
+
+ private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) {
+ final var backupFileFuture = ImportBackupService.read(this, uri);
+ Futures.addCallback(
+ backupFileFuture,
+ new FutureCallback<>() {
+ @Override
+ public void onSuccess(final ImportBackupService.BackupFile backupFile) {
+ showEnterPasswordDialog(backupFile, finishOnCancel);
+ }
+
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ Log.d(Config.LOGTAG, "could not open backup file " + uri, throwable);
+ showBackupThrowable(throwable);
+ }
+ },
+ MainThreadExecutor.getInstance());
+ }
+
+ private void showBackupThrowable(final Throwable throwable) {
+ if (throwable instanceof BackupFileHeader.OutdatedBackupFileVersion) {
+ Snackbar.make(
+ binding.coordinator,
+ R.string.outdated_backup_file_format,
+ Snackbar.LENGTH_LONG)
+ .show();
+ } else if (throwable instanceof IOException
+ || throwable instanceof IllegalArgumentException) {
+ Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG)
+ .show();
+ } else if (throwable instanceof SecurityException e) {
+ Snackbar.make(
+ binding.coordinator,
+ R.string.sharing_application_not_grant_permission,
+ Snackbar.LENGTH_LONG)
+ .show();
+ }
+ }
+
+ private void showEnterPasswordDialog(
+ final ImportBackupService.BackupFile backupFile, final boolean finishOnCancel) {
+ final DialogEnterPasswordBinding enterPasswordBinding =
+ DataBindingUtil.inflate(
+ LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
+ Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri());
+ enterPasswordBinding.explain.setText(
+ getString(
+ R.string.enter_password_to_restore,
+ backupFile.getHeader().getJid().toString()));
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
+ builder.setView(enterPasswordBinding.getRoot());
+ builder.setTitle(R.string.enter_password);
+ builder.setNegativeButton(
+ R.string.cancel,
+ (dialog, which) -> {
+ if (finishOnCancel) {
+ finish();
+ }
+ });
+ builder.setPositiveButton(R.string.restore, null);
+ builder.setCancelable(false);
+ final AlertDialog dialog = builder.create();
+ dialog.setOnShowListener(
+ (d) -> {
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE)
+ .setOnClickListener(
+ v -> {
+ final String password =
+ enterPasswordBinding
+ .accountPassword
+ .getEditableText()
+ .toString();
+ if (password.isEmpty()) {
+ enterPasswordBinding.accountPasswordLayout.setError(
+ getString(R.string.please_enter_password));
+ return;
+ }
+ final Intent intent = getIntent(backupFile, password);
+ setLoadingState(true);
+ ContextCompat.startForegroundService(this, intent);
+ d.dismiss();
+ });
+ });
+ dialog.show();
+ }
+
+ @NonNull
+ private Intent getIntent(ImportBackupService.BackupFile backupFile, String password) {
+ final Uri uri = backupFile.getUri();
+ Intent intent = new Intent(this, ImportBackupService.class);
+ intent.setAction(Intent.ACTION_SEND);
+ intent.putExtra("password", password);
+ if ("file".equals(uri.getScheme())) {
+ intent.putExtra("file", uri.getPath());
+ } else {
+ intent.setData(uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ return intent;
+ }
+
+ private void setLoadingState(final boolean loadingState) {
+ binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE);
+ binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
+ setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ configureActionBar(getSupportActionBar(), !loadingState);
+ this.mLoadingState = loadingState;
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ if (resultCode == RESULT_OK) {
+ if (requestCode == 0xbac) {
+ openBackupFileFromUri(intent.getData(), false);
+ }
+ }
+ }
+
+ @Override
+ public void onAccountAlreadySetup() {
+ runOnUiThread(
+ () -> {
+ setLoadingState(false);
+ Snackbar.make(
+ binding.coordinator,
+ R.string.account_already_setup,
+ Snackbar.LENGTH_LONG)
+ .show();
+ });
+ }
+
+ @Override
+ public void onBackupRestored() {
+ runOnUiThread(
+ () -> {
+ Intent intent = new Intent(this, ConversationActivity.class);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ });
+ }
+
+ @Override
+ public void onBackupDecryptionFailed() {
+ runOnUiThread(
+ () -> {
+ setLoadingState(false);
+ Snackbar.make(
+ binding.coordinator,
+ R.string.unable_to_decrypt_backup,
+ Snackbar.LENGTH_LONG)
+ .show();
+ });
+ }
+
+ @Override
+ public void onBackupRestoreFailed() {
+ runOnUiThread(
+ () -> {
+ setLoadingState(false);
+ Snackbar.make(
+ binding.coordinator,
+ R.string.unable_to_restore_backup,
+ Snackbar.LENGTH_LONG)
+ .show();
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.action_open_backup_file) {
+ openBackupFile();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void openBackupFile() {
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("*/*");
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ startActivityForResult(
+ Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac);
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java
new file mode 100644
index 000000000..b6d4d452e
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java
@@ -0,0 +1,161 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.databinding.DataBindingUtil;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityMagicCreateBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.InstallReferrerUtils;
+import eu.siacs.conversations.xmpp.Jid;
+
+import java.security.SecureRandom;
+
+public class MagicCreateActivity extends XmppActivity implements TextWatcher {
+
+ public static final String EXTRA_DOMAIN = "domain";
+ public static final String EXTRA_PRE_AUTH = "pre_auth";
+ public static final String EXTRA_USERNAME = "username";
+
+ private ActivityMagicCreateBinding binding;
+ private String domain;
+ private String username;
+ private String preAuth;
+
+ @Override
+ protected void refreshUiReal() {}
+
+ @Override
+ protected void onBackendConnected() {}
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ final Intent data = getIntent();
+ this.domain = data == null ? null : data.getStringExtra(EXTRA_DOMAIN);
+ this.preAuth = data == null ? null : data.getStringExtra(EXTRA_PRE_AUTH);
+ this.username = data == null ? null : data.getStringExtra(EXTRA_USERNAME);
+ if (getResources().getBoolean(R.bool.portrait_only)) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ super.onCreate(savedInstanceState);
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_magic_create);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(this.binding.toolbar);
+ configureActionBar(getSupportActionBar(), this.domain == null);
+ if (username != null && domain != null) {
+ binding.title.setText(R.string.your_server_invitation);
+ binding.instructions.setText(getString(R.string.magic_create_text_fixed, domain));
+ binding.username.setEnabled(false);
+ binding.username.setText(this.username);
+ updateFullJidInformation(this.username);
+ } else if (domain != null) {
+ binding.instructions.setText(getString(R.string.magic_create_text_on_x, domain));
+ }
+ binding.createAccount.setOnClickListener(
+ v -> {
+ try {
+ final String username = binding.username.getText().toString();
+ final Jid jid;
+ final boolean fixedUsername;
+ if (this.domain != null && this.username != null) {
+ fixedUsername = true;
+ jid = Jid.ofLocalAndDomainEscaped(this.username, this.domain);
+ } else if (this.domain != null) {
+ fixedUsername = false;
+ jid = Jid.ofLocalAndDomainEscaped(username, this.domain);
+ } else {
+ fixedUsername = false;
+ jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN);
+ }
+ if (!jid.getEscapedLocal().equals(jid.getLocal())
+ || (this.username == null && username.length() < 3)) {
+ binding.usernameLayout.setError(getString(R.string.invalid_username));
+ binding.username.requestFocus();
+ } else {
+ binding.usernameLayout.setError(null);
+ Account account = xmppConnectionService.findAccountByJid(jid);
+ if (account == null) {
+ account =
+ new Account(
+ jid,
+ CryptoHelper.createPassword(new SecureRandom()));
+ account.setOption(Account.OPTION_REGISTER, true);
+ account.setOption(Account.OPTION_DISABLED, true);
+ account.setOption(Account.OPTION_MAGIC_CREATE, true);
+ account.setOption(Account.OPTION_FIXED_USERNAME, fixedUsername);
+ if (this.preAuth != null) {
+ account.setKey(
+ Account.KEY_PRE_AUTH_REGISTRATION_TOKEN, this.preAuth);
+ }
+ xmppConnectionService.createAccount(account);
+ }
+ Intent intent =
+ new Intent(MagicCreateActivity.this, EditAccountActivity.class);
+ intent.putExtra("jid", account.getJid().asBareJid().toString());
+ intent.putExtra("init", true);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ Toast.makeText(
+ MagicCreateActivity.this,
+ R.string.secure_password_generated,
+ Toast.LENGTH_SHORT)
+ .show();
+ StartConversationActivity.addInviteUri(intent, getIntent());
+ startActivity(intent);
+ }
+ } catch (final IllegalArgumentException e) {
+ binding.usernameLayout.setError(getString(R.string.invalid_username));
+ binding.username.requestFocus();
+ }
+ });
+ binding.username.addTextChangedListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ InstallReferrerUtils.markInstallReferrerExecuted(this);
+ super.onDestroy();
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ updateFullJidInformation(s.toString());
+ }
+
+ private void updateFullJidInformation(final String username) {
+ if (username.trim().isEmpty()) {
+ binding.fullJid.setVisibility(View.INVISIBLE);
+ } else {
+ try {
+ binding.fullJid.setVisibility(View.VISIBLE);
+ final Jid jid;
+ if (this.domain == null) {
+ jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN);
+ } else {
+ jid = Jid.ofLocalAndDomainEscaped(username, this.domain);
+ }
+ binding.fullJid.setText(
+ getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
+ binding.usernameLayout.setError(null);
+ } catch (final IllegalArgumentException e) {
+ binding.fullJid.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java
new file mode 100644
index 000000000..2f4c81671
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -0,0 +1,433 @@
+package eu.siacs.conversations.ui;
+
+import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
+import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.databinding.DataBindingUtil;
+
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityManageAccountsBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
+import eu.siacs.conversations.ui.adapter.AccountAdapter;
+import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+
+import org.openintents.openpgp.util.OpenPgpApi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ManageAccountActivity extends XmppActivity
+ implements OnAccountUpdate,
+ KeyChainAliasCallback,
+ XmppConnectionService.OnAccountCreated,
+ AccountAdapter.OnTglAccountState {
+
+ private final String STATE_SELECTED_ACCOUNT = "selected_account";
+
+ private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
+
+ protected Account selectedAccount = null;
+ protected Jid selectedAccountJid = null;
+
+ protected final List accountList = new ArrayList<>();
+ protected AccountAdapter mAccountAdapter;
+ protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
+
+ protected Pair mPostponedActivityResult = null;
+
+ @Override
+ public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ synchronized (this.accountList) {
+ accountList.clear();
+ accountList.addAll(xmppConnectionService.getAccounts());
+ }
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
+ actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
+ }
+ invalidateOptionsMenu();
+ mAccountAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ ActivityManageAccountsBinding binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts);
+
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+ configureActionBar(getSupportActionBar());
+ if (savedInstanceState != null) {
+ String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
+ if (jid != null) {
+ try {
+ this.selectedAccountJid = Jid.ofEscaped(jid);
+ } catch (IllegalArgumentException e) {
+ this.selectedAccountJid = null;
+ }
+ }
+ }
+
+ this.mAccountAdapter = new AccountAdapter(this, accountList);
+ binding.accountList.setAdapter(this.mAccountAdapter);
+ binding.accountList.setOnItemClickListener(
+ (arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
+ registerForContextMenu(binding.accountList);
+ }
+
+
+ @Override
+ public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
+ if (selectedAccount != null) {
+ savedInstanceState.putString(
+ STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString());
+ }
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ ManageAccountActivity.this.getMenuInflater().inflate(R.menu.manageaccounts_context, menu);
+ AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
+ this.selectedAccount = accountList.get(acmi.position);
+ if (this.selectedAccount.isEnabled()) {
+ menu.findItem(R.id.mgmt_account_enable).setVisible(false);
+ menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
+ } else {
+ menu.findItem(R.id.mgmt_account_disable).setVisible(false);
+ menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
+ menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
+ }
+ menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toEscapedString());
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ if (selectedAccountJid != null) {
+ this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid);
+ }
+ refreshUiReal();
+ if (this.mPostponedActivityResult != null) {
+ this.onActivityResult(
+ mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
+ }
+ if (Config.X509_VERIFICATION && this.accountList.isEmpty()) {
+ if (mInvokedAddAccount.compareAndSet(false, true)) {
+ addAccountFromKey();
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.manageaccounts, menu);
+ MenuItem enableAll = menu.findItem(R.id.action_enable_all);
+ MenuItem addAccount = menu.findItem(R.id.action_add_account);
+ MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert);
+
+ if (Config.X509_VERIFICATION) {
+ addAccount.setVisible(false);
+ addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
+
+ if (!accountsLeftToEnable()) {
+ enableAll.setVisible(false);
+ }
+ MenuItem disableAll = menu.findItem(R.id.action_disable_all);
+ if (!accountsLeftToDisable()) {
+ disableAll.setVisible(false);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mgmt_account_publish_avatar:
+ publishAvatar(selectedAccount);
+ return true;
+ case R.id.mgmt_account_disable:
+ disableAccount(selectedAccount);
+ return true;
+ case R.id.mgmt_account_enable:
+ enableAccount(selectedAccount);
+ return true;
+ case R.id.mgmt_account_delete:
+ deleteAccount(selectedAccount);
+ return true;
+ case R.id.mgmt_account_announce_pgp:
+ publishOpenPGPPublicKey(selectedAccount);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void deleteAccount(final Account account) {
+ super.deleteAccount(account);
+ this.selectedAccount = null;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (MenuDoubleTabUtil.shouldIgnoreTap()) {
+ return false;
+ }
+ switch (item.getItemId()) {
+ case R.id.action_add_account:
+ startActivity(new Intent(this, EditAccountActivity.class));
+ break;
+ case R.id.action_import_backup:
+ if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ }
+ break;
+ case R.id.action_disable_all:
+ disableAllAccounts();
+ break;
+ case R.id.action_enable_all:
+ enableAllAccounts();
+ break;
+ case R.id.action_add_account_with_cert:
+ addAccountFromKey();
+ break;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults.length > 0) {
+ if (allGranted(grantResults)) {
+ switch (requestCode) {
+ case REQUEST_IMPORT_BACKUP:
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ break;
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+ if (writeGranted(grantResults, permissions)) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.restartFileObserver();
+ }
+ }
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ if (xmppConnectionService.getConversations().size() == 0) {
+ Intent contactsIntent = new Intent(this, StartConversationActivity.class);
+ contactsIntent.setFlags(
+ // if activity exists in stack, pop the stack and go back to it
+ Intent.FLAG_ACTIVITY_CLEAR_TOP
+ |
+ // otherwise, make a new task for it
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ |
+ // don't use the new activity animation; finish
+ // animation runs instead
+ Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(contactsIntent);
+ finish();
+ return true;
+ } else {
+ return super.onNavigateUp();
+ }
+ }
+
+ @Override
+ public void onClickTglAccountState(Account account, boolean enable) {
+ if (enable) {
+ enableAccount(account);
+ } else {
+ disableAccount(account);
+ }
+ }
+
+ private void addAccountFromKey() {
+ Log.d(Config.LOGTAG, "add account from key");
+ try {
+ KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG)
+ .show();
+ }
+ }
+
+ private void publishAvatar(Account account) {
+ Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
+ startActivity(intent);
+ }
+
+ private void disableAllAccounts() {
+ List list = new ArrayList<>();
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (account.isEnabled()) {
+ list.add(account);
+ }
+ }
+ }
+ for (Account account : list) {
+ disableAccount(account);
+ }
+ }
+
+ private boolean accountsLeftToDisable() {
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (account.isEnabled()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private boolean accountsLeftToEnable() {
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (!account.isEnabled()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private void enableAllAccounts() {
+ List list = new ArrayList<>();
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (!account.isEnabled()) {
+ list.add(account);
+ }
+ }
+ }
+ for (Account account : list) {
+ enableAccount(account);
+ }
+ }
+
+ private void disableAccount(Account account) {
+ account.setOption(Account.OPTION_DISABLED, true);
+ if (!xmppConnectionService.updateAccount(account)) {
+ Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void enableAccount(Account account) {
+ account.setOption(Account.OPTION_DISABLED, false);
+ account.setOption(Account.OPTION_SOFT_DISABLED, false);
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.resetEverything();
+ }
+ if (!xmppConnectionService.updateAccount(account)) {
+ Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void publishOpenPGPPublicKey(Account account) {
+ if (ManageAccountActivity.this.hasPgp()) {
+ announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
+ } else {
+ this.showInstallPgpDialog();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ if (xmppConnectionServiceBound) {
+ if (requestCode == REQUEST_CHOOSE_PGP_ID) {
+ if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
+ selectedAccount.setPgpSignId(
+ data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
+ announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
+ } else {
+ choosePgpSignId(selectedAccount);
+ }
+ } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
+ announcePgp(selectedAccount, null, data, onOpenPGPKeyPublished);
+ }
+ this.mPostponedActivityResult = null;
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, data);
+ }
+ }
+ }
+
+ @Override
+ public void alias(final String alias) {
+ if (Strings.isNullOrEmpty(alias)) {
+ runOnUiThread(
+ () ->
+ Toast.makeText(
+ this,
+ R.string.no_certificate_selected,
+ Toast.LENGTH_LONG)
+ .show());
+ return;
+ }
+ xmppConnectionService.createAccountFromKey(alias, this);
+ }
+
+ @Override
+ public void onAccountCreated(final Account account) {
+ final Intent intent = new Intent(this, EditAccountActivity.class);
+ intent.putExtra("jid", account.getJid().asBareJid().toString());
+ intent.putExtra("init", true);
+ startActivity(intent);
+ }
+
+ @Override
+ public void informUser(final int r) {
+ runOnUiThread(
+ () -> Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show());
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java b/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java
new file mode 100644
index 000000000..f96a17ffc
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java
@@ -0,0 +1,97 @@
+package eu.siacs.conversations.ui;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.databinding.DataBindingUtil;
+
+import java.util.List;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityPickServerBinding;
+import eu.siacs.conversations.entities.Account;
+
+public class PickServerActivity extends XmppActivity {
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ protected void onBackendConnected() {
+
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ startActivity(new Intent(this, WelcomeActivity.class));
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ startActivity(new Intent(this, WelcomeActivity.class));
+ super.onBackPressed();
+ }
+
+ @Override
+ public void onNewIntent(final Intent intent) {
+ super.onNewIntent(intent);
+ if (intent != null) {
+ setIntent(intent);
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ if (getResources().getBoolean(R.bool.portrait_only)) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ super.onCreate(savedInstanceState);
+ ActivityPickServerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_pick_server);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+ configureActionBar(getSupportActionBar());
+ binding.useCim.setOnClickListener(v -> {
+ final Intent intent = new Intent(this, MagicCreateActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ addInviteUri(intent);
+ startActivity(intent);
+ });
+ binding.useOwnProvider.setOnClickListener(v -> {
+ List accounts = xmppConnectionService.getAccounts();
+ Intent intent = new Intent(this, EditAccountActivity.class);
+ intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, true);
+ if (accounts.size() == 1) {
+ intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
+ intent.putExtra("init", true);
+ } else if (!accounts.isEmpty()) {
+ intent = new Intent(this, ManageAccountActivity.class);
+ }
+ addInviteUri(intent);
+ startActivity(intent);
+ });
+
+ }
+
+ public void addInviteUri(Intent intent) {
+ StartConversationActivity.addInviteUri(intent, getIntent());
+ }
+
+ public static void launch(AppCompatActivity activity) {
+ Intent intent = new Intent(activity, PickServerActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ activity.startActivity(intent);
+ activity.overridePendingTransition(0, 0);
+ }
+
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/ShareViaAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ShareViaAccountActivity.java
new file mode 100644
index 000000000..0b20e26c5
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/ShareViaAccountActivity.java
@@ -0,0 +1,81 @@
+package eu.siacs.conversations.ui;
+
+import android.os.Bundle;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.ui.adapter.AccountAdapter;
+import eu.siacs.conversations.xmpp.Jid;
+
+public class ShareViaAccountActivity extends XmppActivity {
+ public static final String EXTRA_CONTACT = "contact";
+ public static final String EXTRA_BODY = "body";
+
+ protected final List accountList = new ArrayList<>();
+ protected ListView accountListView;
+ protected AccountAdapter mAccountAdapter;
+
+ @Override
+ protected void refreshUiReal() {
+ synchronized (this.accountList) {
+ accountList.clear();
+ accountList.addAll(xmppConnectionService.getAccounts());
+ }
+ mAccountAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_manage_accounts);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ configureActionBar(getSupportActionBar());
+ accountListView = findViewById(R.id.account_list);
+ this.mAccountAdapter = new AccountAdapter(this, accountList, false);
+ accountListView.setAdapter(this.mAccountAdapter);
+ accountListView.setOnItemClickListener((arg0, view, position, arg3) -> {
+ final Account account = accountList.get(position);
+ final String body = getIntent().getStringExtra(EXTRA_BODY);
+
+ try {
+ final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT));
+ final Conversation conversation = xmppConnectionService.findOrCreateConversation(
+ account, contact, false, false);
+ switchToConversation(conversation, body);
+ } catch (IllegalArgumentException e) {
+ // ignore error
+ }
+
+ finish();
+ });
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ final int numAccounts = xmppConnectionService.getAccounts().size();
+
+ if (numAccounts == 1) {
+ final String body = getIntent().getStringExtra(EXTRA_BODY);
+ final Account account = xmppConnectionService.getAccounts().get(0);
+
+ try {
+ final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT));
+ final Conversation conversation = xmppConnectionService.findOrCreateConversation(
+ account, contact, false, false);
+ switchToConversation(conversation, body);
+ } catch (IllegalArgumentException e) {
+ // ignore error
+ }
+
+ finish();
+ } else {
+ refreshUiReal();
+ }
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java
new file mode 100644
index 000000000..b2a40976c
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java
@@ -0,0 +1,244 @@
+package eu.siacs.conversations.ui;
+
+import android.Manifest;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.databinding.DataBindingUtil;
+
+import java.util.Arrays;
+import java.util.List;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityWelcomeBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.Compatibility;
+import eu.siacs.conversations.utils.InstallReferrerUtils;
+import eu.siacs.conversations.utils.SignupUtils;
+import eu.siacs.conversations.utils.XmppUri;
+import eu.siacs.conversations.xmpp.Jid;
+
+import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
+import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
+
+import com.google.common.base.Strings;
+
+public class WelcomeActivity extends XmppActivity
+ implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback {
+
+ private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
+
+ private XmppUri inviteUri;
+
+ public static void launch(AppCompatActivity activity) {
+ Intent intent = new Intent(activity, WelcomeActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ activity.startActivity(intent);
+ activity.overridePendingTransition(0, 0);
+ }
+
+ public void onInstallReferrerDiscovered(final Uri referrer) {
+ Log.d(Config.LOGTAG, "welcome activity: on install referrer discovered " + referrer);
+ if ("xmpp".equalsIgnoreCase(referrer.getScheme())) {
+ final XmppUri xmppUri = new XmppUri(referrer);
+ runOnUiThread(() -> processXmppUri(xmppUri));
+ } else {
+ Log.i(Config.LOGTAG, "install referrer was not an XMPP uri");
+ }
+ }
+
+ private void processXmppUri(final XmppUri xmppUri) {
+ if (!xmppUri.isValidJid()) {
+ return;
+ }
+ final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH);
+ final Jid jid = xmppUri.getJid();
+ final Intent intent;
+ if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
+ intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth);
+ } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER)
+ && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) {
+ intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth);
+ intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
+ } else {
+ intent = null;
+ }
+ if (intent != null) {
+ startActivity(intent);
+ finish();
+ return;
+ }
+ this.inviteUri = xmppUri;
+ }
+
+ @Override
+ protected void refreshUiReal() {}
+
+ @Override
+ protected void onBackendConnected() {}
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ new InstallReferrerUtils(this);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public void onNewIntent(final Intent intent) {
+ super.onNewIntent(intent);
+ if (intent != null) {
+ setIntent(intent);
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ if (getResources().getBoolean(R.bool.portrait_only)) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ super.onCreate(savedInstanceState);
+ ActivityWelcomeBinding binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_welcome);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+ configureActionBar(getSupportActionBar(), false);
+ setTitle(null);
+ binding.registerNewAccount.setOnClickListener(
+ v -> {
+ final Intent intent = new Intent(this, PickServerActivity.class);
+ addInviteUri(intent);
+ startActivity(intent);
+ });
+ binding.useExisting.setOnClickListener(
+ v -> {
+ final List accounts = xmppConnectionService.getAccounts();
+ Intent intent = new Intent(this, EditAccountActivity.class);
+ intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false);
+ if (accounts.size() == 1) {
+ intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
+ intent.putExtra("init", true);
+ } else if (!accounts.isEmpty()) {
+ intent = new Intent(this, ManageAccountActivity.class);
+ }
+ addInviteUri(intent);
+ startActivity(intent);
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.welcome_menu, menu);
+ final MenuItem scan = menu.findItem(R.id.action_scan_qr_code);
+ scan.setVisible(Compatibility.hasFeatureCamera(this));
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_import_backup:
+ if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ }
+ break;
+ case R.id.action_scan_qr_code:
+ UriHandlerActivity.scan(this, true);
+ break;
+ case R.id.action_add_account_with_cert:
+ addAccountFromKey();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void addAccountFromKey() {
+ try {
+ KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG)
+ .show();
+ }
+ }
+
+ @Override
+ public void alias(final String alias) {
+ if (Strings.isNullOrEmpty(alias)) {
+ runOnUiThread(
+ () ->
+ Toast.makeText(
+ this,
+ R.string.no_certificate_selected,
+ Toast.LENGTH_LONG)
+ .show());
+ return;
+ }
+ xmppConnectionService.createAccountFromKey(alias, this);
+ }
+
+ @Override
+ public void onAccountCreated(final Account account) {
+ final Intent intent = new Intent(this, EditAccountActivity.class);
+ intent.putExtra("jid", account.getJid().asBareJid().toEscapedString());
+ intent.putExtra("init", true);
+ addInviteUri(intent);
+ startActivity(intent);
+ }
+
+ @Override
+ public void informUser(final int r) {
+ runOnUiThread(() -> Toast.makeText(this, r, Toast.LENGTH_LONG).show());
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
+ if (grantResults.length > 0) {
+ if (allGranted(grantResults)) {
+ switch (requestCode) {
+ case REQUEST_IMPORT_BACKUP:
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ break;
+ }
+ } else if (Arrays.asList(permissions)
+ .contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+ if (writeGranted(grantResults, permissions)) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.restartFileObserver();
+ }
+ }
+ }
+
+ public void addInviteUri(Intent to) {
+ final Intent from = getIntent();
+ if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) {
+ final String invite = from.getStringExtra(StartConversationActivity.EXTRA_INVITE_URI);
+ to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, invite);
+ } else if (this.inviteUri != null) {
+ Log.d(Config.LOGTAG, "injecting referrer uri into on-boarding flow");
+ to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, this.inviteUri.toString());
+ }
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/adapter/BackupFileAdapter.java b/src/conversations/java/eu/siacs/conversations/ui/adapter/BackupFileAdapter.java
new file mode 100644
index 000000000..9f32352ee
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/ui/adapter/BackupFileAdapter.java
@@ -0,0 +1,169 @@
+package eu.siacs.conversations.ui.adapter;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.text.format.DateUtils;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.databinding.DataBindingUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ItemAccountBinding;
+import eu.siacs.conversations.services.AvatarService;
+import eu.siacs.conversations.services.ImportBackupService;
+import eu.siacs.conversations.utils.BackupFileHeader;
+import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xmpp.Jid;
+
+public class BackupFileAdapter extends RecyclerView.Adapter {
+
+ private OnItemClickedListener listener;
+
+ private final List files = new ArrayList<>();
+
+
+ @NonNull
+ @Override
+ public BackupFileViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ return new BackupFileViewHolder(DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_account, viewGroup, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull BackupFileViewHolder backupFileViewHolder, int position) {
+ final ImportBackupService.BackupFile backupFile = files.get(position);
+ final BackupFileHeader header = backupFile.getHeader();
+ backupFileViewHolder.binding.accountJid.setText(header.getJid().asBareJid().toString());
+ backupFileViewHolder.binding.accountStatus.setText(String.format("%s · %s",header.getApp(), DateUtils.formatDateTime(backupFileViewHolder.binding.getRoot().getContext(), header.getTimestamp(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR)));
+ backupFileViewHolder.binding.tglAccountStatus.setVisibility(View.GONE);
+ backupFileViewHolder.binding.getRoot().setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onClick(backupFile);
+ }
+ });
+ loadAvatar(header.getJid(), backupFileViewHolder.binding.accountImage);
+ }
+
+ @Override
+ public int getItemCount() {
+ return files.size();
+ }
+
+ public void setFiles(List files) {
+ this.files.clear();
+ this.files.addAll(files);
+ notifyDataSetChanged();
+ }
+
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ this.listener = listener;
+ }
+
+ static class BackupFileViewHolder extends RecyclerView.ViewHolder {
+ private final ItemAccountBinding binding;
+
+ BackupFileViewHolder(ItemAccountBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ }
+
+ public interface OnItemClickedListener {
+ void onClick(ImportBackupService.BackupFile backupFile);
+ }
+
+ static class BitmapWorkerTask extends AsyncTask {
+ private final WeakReference imageViewReference;
+ private Jid jid = null;
+ private final int size;
+
+ BitmapWorkerTask(final ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ DisplayMetrics metrics = imageView.getContext().getResources().getDisplayMetrics();
+ this.size = ((int) (48 * metrics.density));
+ }
+
+ @Override
+ protected Bitmap doInBackground(Jid... params) {
+ this.jid = params[0];
+ return AvatarService.get(this.jid, size);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ private void loadAvatar(Jid jid, ImageView imageView) {
+ if (cancelPotentialWork(jid, imageView)) {
+ imageView.setBackgroundColor(UIHelper.getColorForName(jid.asBareJid().toString()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getContext().getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(jid);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+
+ private static boolean cancelPotentialWork(Jid jid, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Jid oldJid = bitmapWorkerTask.jid;
+ if (oldJid == null || jid != oldJid) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable asyncDrawable) {
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference bitmapWorkerTaskReference;
+
+ AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java
new file mode 100644
index 000000000..2f7963cf6
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java
@@ -0,0 +1,11 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Context;
+
+import eu.siacs.conversations.xmpp.Jid;
+
+public class PhoneNumberUtilWrapper {
+ public static String toFormattedPhoneNumber(Context context, Jid jid) {
+ throw new AssertionError("This method is not implemented in Conversations");
+ }
+}
diff --git a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java
index 3d639d2f7..593291d95 100644
--- a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java
+++ b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations.utils;
import android.app.Activity;
import android.content.Intent;
+import android.widget.Toast;
import java.util.List;
@@ -19,13 +20,13 @@ public class ProvisioningUtils {
try {
accountConfiguration = AccountConfiguration.parse(json);
} catch (final IllegalArgumentException e) {
- ToastCompat.makeText(activity, R.string.improperly_formatted_provisioning, ToastCompat.LENGTH_LONG).show();
+ Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG).show();
return;
}
final Jid jid = accountConfiguration.getJid();
final List accounts = DatabaseBackend.getInstance(activity).getAccountJids(true);
if (accounts.contains(jid)) {
- ToastCompat.makeText(activity, R.string.account_already_exists, ToastCompat.LENGTH_LONG).show();
+ Toast.makeText(activity, R.string.account_already_exists, Toast.LENGTH_LONG).show();
return;
}
final Intent serviceIntent = new Intent(activity, XmppConnectionService.class);
diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java
new file mode 100644
index 000000000..fb088234a
--- /dev/null
+++ b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java
@@ -0,0 +1,77 @@
+package eu.siacs.conversations.utils;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.ConversationsActivity;
+import eu.siacs.conversations.ui.EditAccountActivity;
+import eu.siacs.conversations.ui.MagicCreateActivity;
+import eu.siacs.conversations.ui.ManageAccountActivity;
+import eu.siacs.conversations.ui.PickServerActivity;
+import eu.siacs.conversations.ui.StartConversationActivity;
+import eu.siacs.conversations.ui.WelcomeActivity;
+import eu.siacs.conversations.xmpp.Jid;
+
+public class SignupUtils {
+
+ public static boolean isSupportTokenRegistry() {
+ return true;
+ }
+
+ public static Intent getTokenRegistrationIntent(final Activity activity, Jid jid, String preAuth) {
+ final Intent intent = new Intent(activity, MagicCreateActivity.class);
+ if (jid.isDomainJid()) {
+ intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString());
+ } else {
+ intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString());
+ intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getEscapedLocal());
+ }
+ intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preAuth);
+ return intent;
+ }
+
+ public static Intent getSignUpIntent(final Activity activity) {
+ return getSignUpIntent(activity, false);
+ }
+
+ public static Intent getSignUpIntent(final Activity activity, final boolean toServerChooser) {
+ final Intent intent;
+ if (toServerChooser) {
+ intent = new Intent(activity, PickServerActivity.class);
+ } else {
+ intent = new Intent(activity, WelcomeActivity.class);
+ }
+ return intent;
+ }
+
+ public static Intent getRedirectionIntent(final ConversationsActivity activity) {
+ final XmppConnectionService service = activity.xmppConnectionService;
+ Account pendingAccount = AccountUtils.getPendingAccount(service);
+ Intent intent;
+ if (pendingAccount != null) {
+ intent = new Intent(activity, EditAccountActivity.class);
+ intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString());
+ if (!pendingAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
+ intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, pendingAccount.isOptionSet(Account.OPTION_REGISTER));
+ }
+ } else {
+ if (service.getAccounts().size() == 0) {
+ if (Config.X509_VERIFICATION) {
+ intent = new Intent(activity, ManageAccountActivity.class);
+ } else if (Config.MAGIC_CREATE_DOMAIN != null) {
+ intent = getSignUpIntent(activity);
+ } else {
+ intent = new Intent(activity, EditAccountActivity.class);
+ }
+ } else {
+ intent = new Intent(activity, StartConversationActivity.class);
+ }
+ }
+ intent.putExtra("init", true);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return intent;
+ }
+}
\ No newline at end of file
diff --git a/src/conversations/new_launcher-web.png b/src/conversations/new_launcher-web.png
new file mode 100644
index 000000000..76057f9fe
Binary files /dev/null and b/src/conversations/new_launcher-web.png differ
diff --git a/src/conversations/res/drawable-hdpi/main_logo.png b/src/conversations/res/drawable-hdpi/main_logo.png
new file mode 100644
index 000000000..42d553dd7
Binary files /dev/null and b/src/conversations/res/drawable-hdpi/main_logo.png differ
diff --git a/src/conversations/res/drawable-hdpi/splash_logo.png b/src/conversations/res/drawable-hdpi/splash_logo.png
new file mode 100644
index 000000000..d8efc71af
Binary files /dev/null and b/src/conversations/res/drawable-hdpi/splash_logo.png differ
diff --git a/src/conversations/res/drawable-mdpi/main_logo.png b/src/conversations/res/drawable-mdpi/main_logo.png
new file mode 100644
index 000000000..d81b73638
Binary files /dev/null and b/src/conversations/res/drawable-mdpi/main_logo.png differ
diff --git a/src/conversations/res/drawable-mdpi/splash_logo.png b/src/conversations/res/drawable-mdpi/splash_logo.png
new file mode 100644
index 000000000..1b10d1f91
Binary files /dev/null and b/src/conversations/res/drawable-mdpi/splash_logo.png differ
diff --git a/src/conversations/res/drawable-xhdpi/main_logo.png b/src/conversations/res/drawable-xhdpi/main_logo.png
new file mode 100644
index 000000000..19fbe70b8
Binary files /dev/null and b/src/conversations/res/drawable-xhdpi/main_logo.png differ
diff --git a/src/conversations/res/drawable-xhdpi/splash_logo.png b/src/conversations/res/drawable-xhdpi/splash_logo.png
new file mode 100644
index 000000000..9458b791c
Binary files /dev/null and b/src/conversations/res/drawable-xhdpi/splash_logo.png differ
diff --git a/src/conversations/res/drawable-xxhdpi/main_logo.png b/src/conversations/res/drawable-xxhdpi/main_logo.png
new file mode 100644
index 000000000..cc091c437
Binary files /dev/null and b/src/conversations/res/drawable-xxhdpi/main_logo.png differ
diff --git a/src/conversations/res/drawable-xxhdpi/splash_logo.png b/src/conversations/res/drawable-xxhdpi/splash_logo.png
new file mode 100644
index 000000000..83c2abe6a
Binary files /dev/null and b/src/conversations/res/drawable-xxhdpi/splash_logo.png differ
diff --git a/src/conversations/res/drawable-xxxhdpi/main_logo.png b/src/conversations/res/drawable-xxxhdpi/main_logo.png
new file mode 100644
index 000000000..3e6ddd877
Binary files /dev/null and b/src/conversations/res/drawable-xxxhdpi/main_logo.png differ
diff --git a/src/conversations/res/drawable-xxxhdpi/splash_logo.png b/src/conversations/res/drawable-xxxhdpi/splash_logo.png
new file mode 100644
index 000000000..349070ba2
Binary files /dev/null and b/src/conversations/res/drawable-xxxhdpi/splash_logo.png differ
diff --git a/src/conversations/res/drawable/ic_app_icon_notification.xml b/src/conversations/res/drawable/ic_app_icon_notification.xml
new file mode 100644
index 000000000..51ccc1fae
--- /dev/null
+++ b/src/conversations/res/drawable/ic_app_icon_notification.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/src/conversations/res/drawable/ic_launcher_foreground.xml b/src/conversations/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 000000000..5851e5f2c
--- /dev/null
+++ b/src/conversations/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/src/conversations/res/drawable/ic_launcher_monochrome.xml b/src/conversations/res/drawable/ic_launcher_monochrome.xml
new file mode 100644
index 000000000..56895d605
--- /dev/null
+++ b/src/conversations/res/drawable/ic_launcher_monochrome.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/src/conversations/res/layout/activity_easy_invite.xml b/src/conversations/res/layout/activity_easy_invite.xml
new file mode 100644
index 000000000..6f1c8fc57
--- /dev/null
+++ b/src/conversations/res/layout/activity_easy_invite.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/conversations/res/layout/activity_import_backup.xml b/src/conversations/res/layout/activity_import_backup.xml
new file mode 100644
index 000000000..7e7c9efc4
--- /dev/null
+++ b/src/conversations/res/layout/activity_import_backup.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/conversations/res/layout/activity_magic_create.xml b/src/conversations/res/layout/activity_magic_create.xml
new file mode 100644
index 000000000..923bd8113
--- /dev/null
+++ b/src/conversations/res/layout/activity_magic_create.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/conversations/res/layout/activity_pick_server.xml b/src/conversations/res/layout/activity_pick_server.xml
new file mode 100644
index 000000000..7d3c9a27e
--- /dev/null
+++ b/src/conversations/res/layout/activity_pick_server.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/conversations/res/layout/activity_welcome.xml b/src/conversations/res/layout/activity_welcome.xml
new file mode 100644
index 000000000..ee1c1a3d2
--- /dev/null
+++ b/src/conversations/res/layout/activity_welcome.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/conversations/res/layout/dialog_enter_password.xml b/src/conversations/res/layout/dialog_enter_password.xml
new file mode 100644
index 000000000..623168aa4
--- /dev/null
+++ b/src/conversations/res/layout/dialog_enter_password.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/conversations/res/menu/easy_onboarding_invite.xml b/src/conversations/res/menu/easy_onboarding_invite.xml
new file mode 100644
index 000000000..89c8ff179
--- /dev/null
+++ b/src/conversations/res/menu/easy_onboarding_invite.xml
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/src/conversations/res/menu/manageaccounts.xml b/src/conversations/res/menu/manageaccounts.xml
new file mode 100644
index 000000000..583f91f6f
--- /dev/null
+++ b/src/conversations/res/menu/manageaccounts.xml
@@ -0,0 +1,31 @@
+
+
\ No newline at end of file
diff --git a/src/conversations/res/menu/welcome_menu.xml b/src/conversations/res/menu/welcome_menu.xml
new file mode 100644
index 000000000..9757cd935
--- /dev/null
+++ b/src/conversations/res/menu/welcome_menu.xml
@@ -0,0 +1,22 @@
+
\ No newline at end of file
diff --git a/src/conversations/res/mipmap-hdpi/new_launcher.png b/src/conversations/res/mipmap-hdpi/new_launcher.png
new file mode 100644
index 000000000..70a2e4ae9
Binary files /dev/null and b/src/conversations/res/mipmap-hdpi/new_launcher.png differ
diff --git a/src/conversations/res/mipmap-hdpi/new_launcher_round.png b/src/conversations/res/mipmap-hdpi/new_launcher_round.png
new file mode 100644
index 000000000..9d4efa725
Binary files /dev/null and b/src/conversations/res/mipmap-hdpi/new_launcher_round.png differ
diff --git a/src/conversations/res/mipmap-mdpi/new_launcher.png b/src/conversations/res/mipmap-mdpi/new_launcher.png
new file mode 100644
index 000000000..30e6c86e1
Binary files /dev/null and b/src/conversations/res/mipmap-mdpi/new_launcher.png differ
diff --git a/src/conversations/res/mipmap-mdpi/new_launcher_round.png b/src/conversations/res/mipmap-mdpi/new_launcher_round.png
new file mode 100644
index 000000000..a196fd228
Binary files /dev/null and b/src/conversations/res/mipmap-mdpi/new_launcher_round.png differ
diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher.png b/src/conversations/res/mipmap-xhdpi/new_launcher.png
new file mode 100644
index 000000000..39b674cb9
Binary files /dev/null and b/src/conversations/res/mipmap-xhdpi/new_launcher.png differ
diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher_round.png b/src/conversations/res/mipmap-xhdpi/new_launcher_round.png
new file mode 100644
index 000000000..039a09fdd
Binary files /dev/null and b/src/conversations/res/mipmap-xhdpi/new_launcher_round.png differ
diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher.png b/src/conversations/res/mipmap-xxhdpi/new_launcher.png
new file mode 100644
index 000000000..a6273f142
Binary files /dev/null and b/src/conversations/res/mipmap-xxhdpi/new_launcher.png differ
diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png b/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png
new file mode 100644
index 000000000..6c10228ec
Binary files /dev/null and b/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png differ
diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher.png b/src/conversations/res/mipmap-xxxhdpi/new_launcher.png
new file mode 100644
index 000000000..1a1105fd7
Binary files /dev/null and b/src/conversations/res/mipmap-xxxhdpi/new_launcher.png differ
diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png b/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png
new file mode 100644
index 000000000..914cad2c4
Binary files /dev/null and b/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png differ
diff --git a/src/conversations/res/values-ar/strings.xml b/src/conversations/res/values-ar/strings.xml
new file mode 100644
index 000000000..b0d52f8f7
--- /dev/null
+++ b/src/conversations/res/values-ar/strings.xml
@@ -0,0 +1,20 @@
+
+
+ اختر مزود خدمة XMPP الخاص بك
+ استخدِم conversations.im
+ أنشئ حسابًا جديدًا
+ هل تملك حساب XMPP؟؟ قد يكون ذلك ممكنا لو كنت تستعمل خدمة XMPP أخرى أو إستعملت تطبيق Conversations سابقا. أو يمكنك صنع حساب XMPP جديد الآن.
+\nملاحظة: بعض خدمات البريد الإلكتروني تقدم حسابات XMPP.
+ XMPP هو مزود مستقل لشبكة المراسلة الفورية. يمكنك استخدام هذا التطبيق مع أي خادم XMPP تختاره.
+\nولكن من أجل راحتك ، فقد جعلنا من السهل إنشاء حساب على موقع chat. مزود مناسب بشكل خاص للاستخدام مع المحادثات.
+ لقد تمت دعوتك إلى%1$s. سنوجهك خلال عملية إنشاء حساب.
+\nعند اختيار%1$s كموفر ، ستتمكن من التواصل مع مستخدمي مقدمي الخدمات الآخرين من خلال منحهم عنوان XMPP الكامل الخاص بك.
+ لقد تمت دعوتك إلى%1$s. تم بالفعل اختيار اسم مستخدم لك. سنوجهك خلال عملية إنشاء حساب.
+\nستتمكن من التواصل مع مستخدمي مقدمي الخدمات الآخرين من خلال منحهم عنوان XMPP الكامل الخاص بك.
+ سيرفر دعوتك
+ لم يتم التقاط الكود بطريقة جيّدة
+ إضغط على زر مشاركة لترسل إلى المتصل بك دعوة إلى %1$s.
+ إذا كان المتصل بك قريبا منك، يمكنه فحص الكود بالأسفل ليقبل دعوتك.
+ إنظم %1$s وتحدّث معي: %2$s
+ شارك الدعوة مع…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-bg/strings.xml b/src/conversations/res/values-bg/strings.xml
new file mode 100644
index 000000000..21f9c8d9a
--- /dev/null
+++ b/src/conversations/res/values-bg/strings.xml
@@ -0,0 +1,18 @@
+
+
+ Изберете своя XMPP доставчик
+ Използвайте conversations.im
+ Създаване не нов профил
+ Имате ли вече XMPP профил? Може да имате, ако вече използвате друг клиент на XMPP или сте използвали Conversations и преди. Ако не, можете да създадете нов XMPP профил сега.\nСъвет: някои доставчици на е-поща също предоставят XMPP профили.
+
+ XMPP е мрежа за общуване чрез мигновени съобщения, която не е обвързана с конкретен доставчик. Можете да използвате клиента с всеки сървър, който работи с XMPP.
+\nЗа Ваше удобство, обаче, ние предоставяме лесен начин да си създадете профил в conversations.im — сървър, пригоден да работи най-добре с Conversations.
+ Получихте покана за %1$s. Ще Ви преведем през процеса на създаване на профил.\nИзбирайки %1$s за доставчик, Вие ще можете да общувате и с потребители на други доставчици, като им предоставите своя пълен XMPP адрес.
+ Получихте покана за %1$s. Вече Ви избрахме потребителско име. Ще Ви преведем през процеса на създаване на профил.\nЩе можете да общувате и с потребители на други доставчици, като им предоставите своя пълен XMPP адрес.
+ Вашата покана за сървъра
+ Неправилно форматиран код за достъп
+ Докоснете бутона за споделяне, за да изпратите на контакта си покана за %1$s.
+ Ако контактът Ви е наблизо, може да сканира кода по-долу, за да приеме поканата Ви.
+ Присъедини се в %1$s и си пиши с мен: %2$s
+ Споделяне на поканата чрез…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-bn-rIN/strings.xml b/src/conversations/res/values-bn-rIN/strings.xml
new file mode 100644
index 000000000..b5fceb87a
--- /dev/null
+++ b/src/conversations/res/values-bn-rIN/strings.xml
@@ -0,0 +1,18 @@
+
+
+ আপনার XMPP প্রোভাইডার নির্বাচন করুন
+ conversations.im ব্যবহার করুন
+ নতুন অ্যকাউন্ট তৈরী করুন
+ আপনার কি কোনও XMPP অ্যকাউন্ট ইতিমধ্যে করা আছে? সেরকমটা হতেই পারে যদি এর আগে আপনি কোনো অন্য XMPP প্রোগ্রাম বা অ্যাপ ব্যবহার করে থাকেন। যদি না করে থাকেন, তাহলে আপনি এখন একটি XMPP অ্যাকাউন্ট বানাতে পারেন।
+\nসূত্র: মাঝে মধ্যে কিছু ইমেল প্রোভাইডাররা XMPP অ্যাকাউন্ট দেয়।
+ XMPP কোনো একটি নির্দিষ্ট সংস্থার উপরে নির্ভরশীল নয়। এই অ্যপটি আপনি যেকোনো সংস্থার XMPP সার্ভারের সাথে ব্যবহার করতে পারেন।
+\nআপনার সুবিধার্থে conversations.im-এ আপনার জন্যে একটি অ্যকাউন্ট তৈরী করে দেওয়া সুবিধাজনক করা হয়েছে। Conversations অ্যপটি এই সার্ভারের সাথে সবথেকে বেশী কার্যকারী।
+ আপনাকে %1$s-এ আমন্ত্রিত করা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\n%1$s ব্যবহার করলেও, অন্য সেবা-প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনি কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে।
+ আপনাকে %1$s-এ নিমন্ত্রণ করা হয়েছে। একটি username-ও আপনার জন্যে নির্দিষ্ট করে রাখা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\nঅন্য XMPP সেবা প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনিও কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে।
+ আপনার নিমন্ত্রণপত্র, সার্ভার থেকে
+ Provisioning code-এ গরমিল আছে
+ Share বোতামটা টিপে %1$s-কে একটি আমন্ত্রপত্র পাঠান
+ পরিচিত ব্যক্তি যদি নিকটেই থাকেন, তাহলে তারা এই কোডটাও স্ক্যান করে নিতে পারেন
+ %1$sতে এসো, আর আমার সাথে কথা বলো: %2$s
+ একটি আমন্ত্রণপত্র দেওয়া যাক...
+
\ No newline at end of file
diff --git a/src/conversations/res/values-ca/strings.xml b/src/conversations/res/values-ca/strings.xml
new file mode 100644
index 000000000..7606e4708
--- /dev/null
+++ b/src/conversations/res/values-ca/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Triï el seu proveïdor de XMPP
+
+ Fer servir conversations.im
+ Crear un compte nou
+ Ja tens un compte XMPP? Aquest podria ser el cas si ja estàs usant un client XMPP diferent o has usat Converses abans. Si no, pots crear un nou compte XMPP ara mateix.\nPista: Alguns proveïdors de correu electrònic també proporcionen comptes XMPP.
+ XMPP és una xarxa de missatgeria instantània independent del proveïdor. Pots usar aquest client amb qualsevol servidor XMPP que triïs. No obstant això, per a la teva conveniència, hem fet fàcil la creació d\'un compte en Conversaciones.im; un proveïdor especialment adequat per a l\'ús amb Conversations.
+ Has estat convidat a %1$s. Et guiarem a través del procés de creació d\'un compte.\nEn triar%1$s com a proveïdor podràs comunicar-se amb els usuaris d\'altres proveïdors donant-los la seva adreça XMPP completa.
+ Has estat convidat a %1$s . Ja s\'ha triat un nom d\'usuari per a tu. Et guiarem en el procés de creació d\'un compte. Podràs comunicar-te amb usuaris d\'altres proveïdors donant-los la teva adreça XMPP completa.
+ La teva invitació al servidor
+ Codi d\'aprovisionament mal formatat
+ Toca el botó de compartir per a enviar al teu contacte una invitació a %1$s .
+ Si el teu contacte està a prop, també pot escanejar el codi de baix per a acceptar la teva invitació.
+ Uneix-te %1$s i xerra amb mi: %2$s
+ Comparteix la invitació amb...
+
\ No newline at end of file
diff --git a/src/conversations/res/values-da-rDK/strings.xml b/src/conversations/res/values-da-rDK/strings.xml
new file mode 100644
index 000000000..28f460eaf
--- /dev/null
+++ b/src/conversations/res/values-da-rDK/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Vælg din XMPP udbyder
+ Brug conversations.im
+ Opret ny konto
+ Har du allerede en XMPP-konto? Dette kan være tilfældet, hvis du allerede bruger en anden XMPP-klient eller har brugt Conversations før. Hvis ikke, kan du lige nu oprette en ny XMPP-konto.\nTip: Nogle e-mail-udbydere leverer også XMPP-konti.
+ XMPP er et udbyderuafhængigt onlinemeddelelsesnetværk. Du kan bruge denne klient med hvilken XMPP-server du end vælger.
+\nMen for din nemhedsskyld har vi gjort vi det let at oprette en konto på conversations.im; en udbyder, der er specielt velegnet til brug med Conversations.
+ Du er blevet inviteret til %1$s. Vi guider dig gennem processen med at oprette en konto.\nNår du vælger %1$s som udbyder, kan du kommunikere med brugere fra andre udbydere ved at give dem din fulde XMPP-adresse.
+ Du er blevet inviteret til %1$s. Der er allerede valgt et brugernavn til dig. Vi guider dig gennem processen med at oprette en konto.\nDu vil være i stand til at kommunikere med brugere fra andre udbydere ved at give dem din fulde XMPP-adresse.
+ Din server invitation
+ Forkert formateret klargøringskode
+ Tryk på deleknappen for at sende din kontakt en invitation til %1$s.
+ Hvis din kontakt er i nærheden, kan de også skanne koden nedenfor for at acceptere din invitation.
+ Deltag med %1$s og chat med mig: %2$s
+ Del invitation med…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-de/strings.xml b/src/conversations/res/values-de/strings.xml
new file mode 100644
index 000000000..f3a547897
--- /dev/null
+++ b/src/conversations/res/values-de/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Wähle deinen XMPP-Provider
+ Benutze conversations.im
+ Neues Konto erstellen
+ Hast du bereits ein XMPP-Konto? Dies kann der Fall sein, wenn du bereits einen anderen XMPP-Client verwendest oder bereits Conversations verwendet hast. Wenn nicht, kannst du jetzt ein neues XMPP-Konto erstellen.\nTipp: Einige E-Mail-Anbieter bieten auch XMPP-Konten an.
+ XMPP ist ein anbieterunabhängiges Instant Messaging Netzwerk. Du kannst diese App mit jedem beliebigen XMPP-Server nutzen.
+\nUm es dir leicht zu machen, haben wir die Möglichkeit geschaffen, ein Konto auf conversations.im anzulegen; ein Anbieter, der speziell für die Verwendung mit Conversations geeignet ist.
+ Du wurdest zu %1$s eingeladen. Wir führen dich durch den Prozess der Kontoerstellung.\nWenn du %1$s als Provider wählst, kannst du mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst.
+ Du wurdest zu %1$seingeladen. Ein Benutzername ist bereits für dich ausgewählt worden. Wir führen dich durch den Prozess der Kontoerstellung.\nDu kannst mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst.
+ Deine Einladung für den Server
+ Falsch formatierter Provisionierungscode
+ Tippe auf die \"Teilen\"-Schaltfläche, um deinem Kontakt eine Einladung an %1$s zu senden.
+ Wenn dein Kontakt in der Nähe ist, kann er auch den untenstehenden Code einscannen, um deine Einladung anzunehmen.
+ Komme zu %1$s und chatte mit mir: %2$s
+ Einladung teilen mit…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-el/strings.xml b/src/conversations/res/values-el/strings.xml
new file mode 100644
index 000000000..7dbc19056
--- /dev/null
+++ b/src/conversations/res/values-el/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Επιλέξτε τον πάροχο XMPP σας
+ Χρήση του conversations.im
+ Δημιουργία νέου λογαριασμού
+ Έχετε ήδη λογαριασμό XMPP; Αυτό μπορεί να συμβαίνει αν ήδη χρησιμοποιείτε ένα άλλο πρόγραμμα XMPP ή έχετε χρησιμοποιήσει το Conversations παλιότερα. Αν όχι, μπορείτε να δημιουργήσετε ένα νέο λογαριασμό XMPP τώρα.\nΧρήσιμη πληροφορία: Κάποιοι πάροχοι e-mail παρέχουν επίσης και λογαριασμούς XMPP.
+ Το XMPP είναι ένα δίκτυο άμεσης ανταλλαγής μηνυμάτων ανεξάρτητο παρόχου. Μπορείτε να χρησιμοποιήσετε αυτό το πρόγραμμα με όποιον διακομιστή XMPP επιθυμείτε.
+\nΓια διευκόλυνση πάντως μπορείτε να δημιουργήσετε έναν λογαριασμό στο conversations.im, έναν πάροχο ειδικά σχεδιασμένο για χρήση με το Conversations.
+ Έχετε προσκληθεί στο %1$s. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΕπιλέγοντας τον %1$s ως πάροχο θα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.
+ Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΘα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.
+ Η πρόσκλησή σας στον διακομιστή
+ Λάθος μορφοποίηση κώδικα παροχής
+ Πατήστε το πλήκτρο διαμοιρασμού για να στείλετε στην επαφή σας μια πρόσκληση στο %1$s.
+ Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σαρώσει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας.
+ Μπείτε στο %1$s και συνομιλήστε μαζί μου: %2$s
+ Διαμοιρασμός πρόσκλησης με…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-eo/strings.xml b/src/conversations/res/values-eo/strings.xml
new file mode 100644
index 000000000..d5fc7d80e
--- /dev/null
+++ b/src/conversations/res/values-eo/strings.xml
@@ -0,0 +1,20 @@
+
+
+ Uzi conversations.im
+ Aliĝu al %1$s kaj babilu kun mi: %2$s
+ Vi estis invitita al %1$s. Ni gvidos vin tra la procezo de kreado de konto.
+\nElektante %1$s kiel provizanton vi povos komuniki kun uzantoj de aliaj provizantoj donante al ili vian plenan XMPP-adreson.
+ Ĉu vi jam havas XMPP-konton\? Ĉi tio povus esti la kazo se vi jam uzas alian XMPP-klienton aŭ antaŭe uzis Conversations. Se ne, vi povas krei novan XMPP-konton nun.
+\nKonsileto: Iuj retpoŝtaj provizantoj ankaŭ provizas XMPP-kontojn.
+ Se via kontakto estas proksime, ili ankaŭ povas skani la suban kodon por akcepti vian inviton.
+ Elekti vian XMPP-provizanton
+ Kunhavigi inviton kun…
+ XMPP estas provizanta sendependa tujmesaĝa reto. Vi povas uzi ĉi tiun klienton per kia ajn XMPP-servilo, kiun vi elektas.
+\nTamen por via komforto ni faciligis krei konton ĉe conversations.im; provizanto speciale taŭga por la uzo kun Conversations.
+ Vi estis invitita al %1$s. Uzantnomo jam estas elektita por vi. Ni gvidos vin tra la procezo de kreado de konto.
+\nVi povos komuniki kun uzantoj de aliaj provizantoj donante al ili vian plenan XMPP-adreson.
+ Nedece formatita provizokodo
+ Premu la kunhavigi butonon por sendi al via kontakto inviton al %1$s.
+ Via servila invito
+ Krei novan konton
+
\ No newline at end of file
diff --git a/src/conversations/res/values-es/strings.xml b/src/conversations/res/values-es/strings.xml
new file mode 100644
index 000000000..53d97d6e9
--- /dev/null
+++ b/src/conversations/res/values-es/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Elige tu proveedor XMPP
+ Usa conversations.im
+ Crear nueva cuenta
+ ¿Ya tienes una cuenta XMPP? Este puede ser el caso si ya estás usando un cliente XMPP diferente o has usado Conversations anteriormente. Si no es así, puedes crear una nueva cuenta XMPP ahora mismo.\nConsejo: Algunos proveedores de email también ofrecen una cuenta XMPP.
+ XMPP es una red de mensajería instantánea independiente del proveedor. Puedes utilizar esta aplicación con cualquier servidor XMPP que elijas.
+\nSin embargo, para tu comodidad, te facilitamos la creación de una cuenta en conversations.im, un proveedor específicamente adaptado para su uso con Conversations.
+ Has sido invitado a %1$s. Te guiaremos durante el proceso de creación de la cuenta.\nCuando selecciones %1$s como proveedor podrás comunicarte con usuarios de otros servidores proporcionándoles tu dirección XMPP completa.
+ Has sido invitado a %1$s. Un nombre de usuario ya ha sido escogido para ti. Te guiaremos durante el proceso de creación de la cuenta.\nPodrás comunicarte con otros usuarios de otros servidores proporcionándoles tu dirección XMPP completa.
+ Tu invitación al servidor
+ Código de abastecimiento formateado incorrectamente
+ Pulsa el botón de compartir para enviar a tu contacto una invitación a %1$s.
+ Si tu contacto está cerca, también puede escanear el código mostrado debajo para aceptar tu invitación.
+ Únete a %1$s y chatea conmigo: %2$s
+ Comparte la invitación con…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-eu/strings.xml b/src/conversations/res/values-eu/strings.xml
new file mode 100644
index 000000000..a8efd240d
--- /dev/null
+++ b/src/conversations/res/values-eu/strings.xml
@@ -0,0 +1,9 @@
+
+
+ Hautatu zure XMPP hornitzailea
+ Erabili conversations.im
+ Kontu berria sortu
+ XMPP kontu bat badaukazu dagoeneko? Horrela izan daiteke beste XMPP aplikazio bat erabiltzen baduzu edo Conversations lehenago erabili baduzu. Bestela XMPP kontu berri bat sortu dezakezu oraintxe bertan.\nIradokizuna: email hornitzaile batzuek XMPP kontuak hornitzen dituzte ere.
+ XMPP hornitzailez independientea den bat-bateko mezularitza sare bat da. Aplikazio hau nahi duzun XMPP zerbitzariarekin erabili dezakezu.
+\nHala ere zure erosotasunerako conversations.im-en, Conversationsekin bereziki erabiltzeko egokia den hornitzaile batean, kontu bat sortzea erraz egin dugu.
+
\ No newline at end of file
diff --git a/src/conversations/res/values-fa-rIR/strings.xml b/src/conversations/res/values-fa-rIR/strings.xml
new file mode 100644
index 000000000..dcf72204c
--- /dev/null
+++ b/src/conversations/res/values-fa-rIR/strings.xml
@@ -0,0 +1,20 @@
+
+
+ لطفا سرویس دهنده پیام خود را انتخاب نمائید. برای مثال artalk.im
+ از Conversations.im استفاده کنید
+ حساب کاربری جدیدی بسازید
+ عضو %1$s شو و با من گفتگو کن: %2$s
+ شما به %1$s دعوت شدهاید. ما شما را در ساخت حسابتان راهنمایی میکنیم.
+\nوقتی شما %1$s را به عنوان سرویسدهندهٔ خود برگزینید، خواهید توانست با کاربران بقیهٔ سرویسدهندهها نیز ارتباط داشته باشید. کافی است نشانی کامل XMPP خود را به آنها بدهید.
+ آیا خودتان حساب XMPP دارید؟ اگر جایی دیگری با همین برنامه یا برنامههای مشابه کار میکنید باید داشته باشید. اگر حساب XMPP ندارید، همینجا میتوانید یکی بسازید.
+\nنکته: برخی از سرویسدهندههای ایمیل به شما حساب XMPP هم ارائه میدهند.
+ اگر مخاطب شما نزدیکتان است، میتواند برای پذیرش دعوتنامه کد زیر را اسکن کند.
+ همرسانی دعوتنامه با…
+ شبکهٔ XMPP مستقل از سرویسدهندههایش است. بنابراین شما میتوانید این برنامه را با هر سرویسدهندهای که میخواهید به کار ببرید.
+\nولی برای راحتی شما، ما امکان ساخت حساب روی سرور conversations.im را میدهیم؛ سرویسدهندهای که برای کار با این برنامه بهینه شده است.
+ شما به %1$s دعوت شدهاید و یک نام کاربری برایتان برگزیده شده است. ما شما را در ساخت حسابتان راهنمایی میکنیم.
+\nشما خواهید توانست با کاربران بقیهٔ سرویسدهندهها نیز ارتباط داشته باشید. کافی است نشانی کامل XMPP خود را به آنها بدهید.
+ کد مورد نظر اشتباه ساخته شده است
+ روی گزینهٔ همرسانی بزنید تا مخاطب خود را به %1$s دعوت کنید.
+ دعوتنامهٔ سرور شما
+
\ No newline at end of file
diff --git a/src/conversations/res/values-fi/strings.xml b/src/conversations/res/values-fi/strings.xml
new file mode 100644
index 000000000..c416b2555
--- /dev/null
+++ b/src/conversations/res/values-fi/strings.xml
@@ -0,0 +1,15 @@
+
+
+ Valitse XMPP-palveluntarjoaja
+ Käytä conversations.im:ää
+ Luo uusi tili
+ Onko sinulla jo XMPP-tunnus? Jos käytät jo toista XMPP-sovellusta tai olet käyttänyt Conversationsia aiemmin, niin voi olla. Jos ei, voit tehdä uuden XMPP-tilin saman tien.\nVinkki: Jotkin sähköpostipalvelut tarjoavat myös XMPP-tilin.
+ XMPP on tietystä palveluntarjoasta riippumaton pikaviestiverkosto. Voit käyttää tätä asiakasohjelmaa minkä tahansa haluamasi XMPP-palvelimen kanssa.
+\nHelppouden nimissä olemme kuitenkin helpottaneet tilin luomista conversations.im:iin.
+ Sinut on kutsuttu %1$s:iin. Opastamme sinua tilin luomisen kanssa.\nValitessasi palvelimen %1$s palveluntarjoajaksesi voit jutella muiden palveluntajoajien käyttäjien kanssa kertomalla heille koko XMPP-osoitteesi.
+ Sinut on kutsuttu palvelimelle %1$s. Käyttäjänimesi on valittu valmiiksi puolestasi. Opastamme sinua tilin luomisen kanssa.\nVoit jutella muiden palveluntarjoajien käyttäjien kanssa kertomalle heille koko XMPP-osoitteesi.
+ Kutsusi palvelimelle
+ Virheellisesti muotoiltu koodi
+ Jos henkilö on lähellä, hän voi myös hyväksyä kutsun lukemalla allaolevan koodin.
+ Jaa kutsu sovelluksella...
+
\ No newline at end of file
diff --git a/src/conversations/res/values-fr/strings.xml b/src/conversations/res/values-fr/strings.xml
new file mode 100644
index 000000000..68de25b54
--- /dev/null
+++ b/src/conversations/res/values-fr/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Choisissez votre fournisseur XMPP
+ Utiliser conversations.im
+ Créer un nouveau compte
+ Avez-vous déjà un compte XMPP ? Cela peut être le cas si vous utilisez déjà un autre client XMPP ou si vous avez déjà utilisé Conversations auparavant. Sinon, vous pouvez créer un nouveau compte XMPP dès maintenant.\nRemarque : Certains fournisseurs de messagerie proposent également des comptes XMPP.
+ XMPP est un réseau de messagerie instantanée indépendant du fournisseur. Vous pouvez utiliser cette application avec n’importe quel serveur XMPP de votre choix.
+\nToutefois, pour votre commodité, nous avons facilité la création d’un compte sur conversations.im ; un fournisseur spécialement conçu pour Conversations.
+ Vous avez été invité à %1$s. Nous allons vous guider à travers le processus de création d’un compte.\nEn choisissant %1$s comme fournisseur, vous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète.
+ Vous avez été invité à %1$s. Un nom d’utilisateur a déjà été choisi pour vous. Nous allons vous guider à travers le processus de création d’un compte.\nVous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète.
+ Votre invitation au serveur
+ Code de provisionnement mal formaté
+ Appuyez sur le bouton partager pour envoyer à votre contact une invitation pour %1$s.
+ Si vos contacts sont à proximité, ils peuvent aussi scanner le code ci-dessous pour accepter votre invitation.
+ Rejoignez %1$s et discutez avec moi : %2$s
+ Partager une invitation avec …
+
\ No newline at end of file
diff --git a/src/conversations/res/values-gl/strings.xml b/src/conversations/res/values-gl/strings.xml
new file mode 100644
index 000000000..c36e20c55
--- /dev/null
+++ b/src/conversations/res/values-gl/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Elixe o teu provedor XMPP
+ Utilizar conversations.im
+ Crear nova conta
+ Xa posúes unha conta XMPP? Este pode ser o caso se xa estás a utilizar outro cliente XMPP ou utilizaches Conversations previamente. Se non é así podes crear unha nova conta agora mesmo.\nTruco: Algúns provedores de correo tamén proporcionan contas XMPP.
+ XMPP é unha rede de mensaxería independente-do-provedor. Podes utilizar esta app con calquera provedor XMPP da túa elección.
+\nEmporiso, pola tua comenencia, fixemos que fose doado crear unha conta en conversations.im; un provedor moi axeitado para utilizar con Conversations.
+ Convidáronte a %1$s. Guiarémoste no proceso para crear unha conta.\nAo elexir %1$s como provedor poderás comunicarte con usuarias doutros provedores cando lles deas o teu enderezo XMPP completo.
+ Convidáronte a %1$s. Xa eleximos un nome de usuaria para ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias doutros provedores cando lles digas o teu enderezo XMPP completo.
+ O convite do teu servidor
+ Código de aprovisionamento con formato non válido
+ Toca no botón compartir para convidar ao teu contacto a %1$s.
+ Se o contacto está preto de ti, pode escanear o código inferior para aceptar o teu convite.
+ Únete a %1$s e conversa conmigo: %2$s
+ Enviar convite a…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-hr/strings.xml b/src/conversations/res/values-hr/strings.xml
new file mode 100644
index 000000000..21b0823be
--- /dev/null
+++ b/src/conversations/res/values-hr/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Odaberite svog XMPP davatelja usluga.
+ Koristite conversations.im
+ Napravi novi račun
+ Već imate XMPP račun? To može biti slučaj ako već koristite drugi XMPP klijent ili ste prije koristili Razgovore. Ako niste, možete odmah stvoriti novi XMPP račun.\nSavjet: Neki pružatelji usluga e-pošte također nude XMPP račune.
+ XMPP je mreža za razmjenu izravnih poruka neovisna o pružatelju usluga. Možete koristiti ovaj klijent s bilo kojim XMPP poslužiteljem koji odaberete.
+\nMeđutim, radi vaše udobnosti olakšali smo kreiranje računa na conversations.im; pružatelj usluga posebno prilagođen za korištenje s Conversations.
+ Pozvani ste na %1$s. Vodit ćemo vas kroz postupak kreiranja računa.\nPrilikom odabira %1$s pružatelja moći ćete komunicirati s korisnicima drugih pružatelja dajući im svoju punu XMPP adresu.
+ Pozvani ste na %1$s. Korisničko ime je već odabrano za vas. Vodit ćemo vas kroz postupak kreiranja računa.\nMoći ćete komunicirati s korisnicima drugih pružatelja tako da im date svoju punu XMPP adresu.
+ Vaša pozivnica za poslužitelj
+ Neispravno formatiran kod za dodjelu
+ Dodirnite gumb za dijeljenje kako biste svom kontaktu poslali pozivnicu na %1$s.
+ Ako je vaš kontakt u blizini, također može skenirati kod u nastavku kako bi prihvatio vašu pozivnicu.
+ Pridružite se %1$s i razgovarajte sa mnom: %2$s
+ Podijelite pozivnicu s...
+
\ No newline at end of file
diff --git a/src/conversations/res/values-hu/strings.xml b/src/conversations/res/values-hu/strings.xml
new file mode 100644
index 000000000..272809007
--- /dev/null
+++ b/src/conversations/res/values-hu/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Válassza ki az XMPP szolgáltatóját
+ A conversations.im használata
+ Új fiók létrehozása
+ Már rendelkezik XMPP-fiókkal? Ez az eset állhat fenn, ha már egy másik XMPP-klienst használ, vagy ha már korábban használta a Conversations alkalmazást. Ha nem, akkor most létrehozhat egy új XMPP-fiókot.\nTipp: egyes e-mail szolgáltatók is biztosítanak XMPP-fiókokat.
+ Az XMPP egy szolgáltatófüggetlen, azonnali üzenetküldő hálózat. Ezt a kliensprogramot bármely XMPP-kiszolgálóhoz használhatja.
+\nAzonban a kényelem érdekében megkönnyítettük a conversations.im szolgáltatón való fióklétrehozást, ami kifejezetten a Conversations alkalmazással történő használatra lett tervezve.
+ Meghívást kapott a(z) %1$s kiszolgálóra. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nHa a(z) %1$s kiszolgálót választja szolgáltatóként, akkor képes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.
+ Meghívást kapott a(z) %1$s kiszolgálóra. Már kiválasztottak Önnek egy felhasználónevet. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nKépes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.
+ Az Ön kiszolgálómeghívása
+ Helytelenül formázott kiépítési kód
+ Koppintson a megosztás gombra, hogy meghívót küldjön a partnerének erre: %1$s.
+ Ha a partnere a közelben van, akkor a meghívás elfogadásához leolvashatja a lenti kódot.
+ Csatlakozzon ehhez: %1$s, és csevegjen velem: %2$s
+ Meghívás megosztása…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-id/strings.xml b/src/conversations/res/values-id/strings.xml
new file mode 100644
index 000000000..a316ee848
--- /dev/null
+++ b/src/conversations/res/values-id/strings.xml
@@ -0,0 +1,16 @@
+
+
+ Pilih XMPP server anda
+ Gunakan conversations.im
+ Buat akun baru
+ Anda sudah memiliki akun XMPP\? Ini mungkin terjadi jika Anda sudah menggunakan aplikasi XMPP yang berbeda atau pernah menggunakan Conversations sebelumnya. Jika tidak, Anda dapat membuat akun XMPP baru. NPetunjuk: Beberapa penyedia layanan email juga menyediakan akun XMPP.
+ XMPP adalah jaringan penyedia pesan instan independen. Anda dapat menggunakan aplikasi ini dengan server XMPP pilihan Anda. NNamun demi kenyamanan Anda, kami permudah untuk membuat akun di Conversations.im; provider yang sangat cocok digunakan dengan Conversations.
+ Anda telah diundang ke %1$s. Kami akan memandu Anda melalui proses pembuatan akun. \nSaat memilih %1$s sebagai penyedia, Anda akan dapat berkomunikasi dengan pengguna provider lain dengan memberikan alamat XMPP lengkap Anda kepada mereka.
+ Anda telah diundang ke%1$s. Username telah dipilihkan untuk Anda. Kami akan memandu Anda melalui proses pembuatan akun. \nAnda dapat berkomunikasi dengan pengguna provider lain dengan memberi mereka alamat XMPP lengkap Anda.
+ Undangan server Anda
+ Kode provisioning tidak diformat dengan benar
+ Klik tombol bagikan untuk mengirim undangan ke kontak Anda %1$s.
+ Jika kontak Anda di dekat Anda, mereka juga dapat memindai kode di bawah ini untuk menerima undangan Anda
+ Bergabung %1$s dan mengobrol dengan saya: %2$s
+ Bagikan undangan dengan...
+
\ No newline at end of file
diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml
new file mode 100644
index 000000000..3116332d3
--- /dev/null
+++ b/src/conversations/res/values-it/strings.xml
@@ -0,0 +1,18 @@
+
+
+ Scegli il tuo fornitore XMPP
+ Usa conversations.im
+ Crea un nuovo profilo
+ Hai già un profilo XMPP\? Può accadere se stai già usando un client XMPP diverso o hai già usato prima Conversations. In caso negativo, puoi creare un profilo XMPP adesso.
+\nNota: alcuni fornitori di email offrono anche account XMPP.
+ XMPP è una rete di messaggistica istantanea indipendente dal fornitore. Puoi usare questa app con qualsiasi server XMPP.
+\nTuttavia, per comodità, puoi creare facilmente un account su conversations.im; un fornitore pensato apposta per essere usato con Conversations.
+ Hai ricevuto un invito per %1$s. Ti guideremo nel procedimento per creare un profilo.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.
+ Hai ricevuto un invito per %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un profilo.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.
+ Il tuo invito al server
+ Codice di approvvigionamento formattato male
+ Tocca il pulsante condividi per inviare al contatto un invito per %1$s.
+ Se il contatto è vicino, può anche scansionare il codice sottostante per accettare il tuo invito.
+ Unisciti a %1$s e chatta con me: %2$s
+ Condividi invito con…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-ja/strings.xml b/src/conversations/res/values-ja/strings.xml
new file mode 100644
index 000000000..851af0d4a
--- /dev/null
+++ b/src/conversations/res/values-ja/strings.xml
@@ -0,0 +1,17 @@
+
+
+ XMPP プロバイダーを選択してください
+ conversations.im を利用する
+ 新規アカウントを作成
+ XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新規 XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。
+ XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このアプリを使用することができます。
+\nよろしければ、 Conversations に最適化されたプロバイダー conversations.im で簡単にアカウントを作成することもできます。
+ %1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。
+ %1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。
+ サーバーの招待
+ 仮コードの書式が不正です
+ 共有ボタンを叩いて、連絡先の %1$s に招待を送信する。
+ あなたの連絡先が近くにいる場合は、下のコードをスキャンして、あなたの招待を受け取ることもできます。
+ %1$s に参加して私とお話しましょう: %2$s
+ …で招待を共有
+
\ No newline at end of file
diff --git a/src/conversations/res/values-nl/strings.xml b/src/conversations/res/values-nl/strings.xml
new file mode 100644
index 000000000..30e90fe6d
--- /dev/null
+++ b/src/conversations/res/values-nl/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Kies je XMPP-dienst
+ Conversations.im gebruiken
+ Nieuwe account registreren
+ Heb je al een XMPP-account? Als je al een andere XMPP-cliënt gebruikt, of Conversations vroeger al eens hebt gebruikt, is dit waarschijnlijk het geval. Zo niet, kan je nu een nieuwe XMPP-account aanmaken.\nTip: sommige e-mailproviders bieden ook XMPP-accounts aan.
+ XMPP is een provider-onafhankelijk berichtennetwerk. Je kan deze applicatie gebruiken met eender welke XMPP-server.
+\nOm het je gemakkelijker te maken kun je simpelweg een account aanmaken op conversations.im; een provider speciaal geschikt voor Conversations.
+ Je ontving een uitnodiging voor %1$s. We zullen je helpen een account aan te maken.\nWanneer je %1$s als je provider kiest kan je met gebruikers van andere providers communiceren door hen je volledige XMPP-adres te geven.
+ Je ontving een uitnodiging voor %1$s. Er werd reeds een gebruikersnaam voor jou gekozen. We zullen je helpen een account aan te maken.\nJe zal met gebruikers van andere providers communiceren door hen je volledige XMPP-adres te geven.
+ Je server uitnodiging
+ Tik op de delen knop om een uitnodiging te versturen naar %1$s.
+ Als je contactpersoon in de buurt is, kan deze ook onderstaande code scannen om de uitnodiging te aanvaarden.
+ Deel de uitnodiging met…
+ Vergezel %1$s en chat met mij: %2$s
+ Onjuist geformatteerde provisioningcode
+
\ No newline at end of file
diff --git a/src/conversations/res/values-pl/strings.xml b/src/conversations/res/values-pl/strings.xml
new file mode 100644
index 000000000..62a7f4104
--- /dev/null
+++ b/src/conversations/res/values-pl/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Wybierz dostawcę XMPP
+ Użyj conversations.im
+ Utwórz nowe konto
+ Czy masz już konto XMPP? Tak może być jeśli używasz już innego klienta XMPP lub używałeś już Conversations. Jeśli nie możesz stworzyć nowe konto XMPP teraz.\nPodpowiedź: Niektórzy dostawcy poczty oferują również konta XMPP.
+ XMPP to niezależna od dostawcy sieć komunikacji błyskawicznej. Możesz użyć tej aplikacji z dowolnym serwerem XMPP.
+\nDla twojej wygody jednak ułatwiliśmy stworzenie konta na conversations.im; dostawcy specjalnie dostosowanego do pracy z Conversations.
+ Zostałeś zaproszony do %1$s. Poprowadzimy ciebie przez proces tworzenia konta.\nWybierając %1$s jako dostawcę będziesz mógł komunikować się z innymi użytkownikami podając swój pełny adres XMPP.
+ Zostałeś zaproszony do %1$s. Nazwa użytkownika została już dla ciebie wybrana. Poprowadzimy ciebie przez proces tworzenia konta.\nBęziesz mógł komunikować się z innymi użytkownikami podając swój adres XMPP.
+ Zaproszenie twojego serwera
+ Niepoprawnie sformatowany kod zaopatrywania
+ Użyj przycisku udostępniania aby wysłać swojemu kontaktowi zaproszenie do %1$s.
+ Jeśli twój kontakt jest blisko może przeskanować kod poniżej aby zaakceptować twoje zaproszenie.
+ Dołącz do %1$s aby porozmawiać ze mną: %2$s
+ Udostępnij zaproszenie…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml
new file mode 100644
index 000000000..210d814e1
--- /dev/null
+++ b/src/conversations/res/values-pt-rBR/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Selecione o seu provedor XMPP
+ Usar o conversations.im
+ Criar uma nova conta
+ Você já possui uma conta XMPP? Esse pode ser o seu caso caso já esteja usando um outro cliente XMPP ou tenha usado o Conversations antes. Caso contrário, você pode criar uma nova conta XMPP agora.\nDica: alguns provedores de e-mail também fornecem contas XMPP.
+ O XMPP é uma rede de mensageria instantânea independente de provedor. Você pode usar esse cliente com qualquer servidor XMPP que você escolher.
+\nEntretanto, para sua conveniência, nós simplificamos o processo de criação de uma conta em conversations.im, um provedor especialmente configurado para se usar com o Conversations.
+ Você foi convidado para %1$s. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nAo escolher %1$s como um provedor você conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo.
+ Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo.
+ Seu convite do servidor
+ Código de provisionamento formatado de maneira imprópria
+ Toque no botão compartilhar para enviar, para seu contato, um convite para %1$s.
+ Se seu contato estiver por perto, ele também pode escanear o código abaixo para aceitar seu convite.
+ Junte-se a %1$s e converse comigo: %2$s
+ Compartilhe o convite com...
+
\ No newline at end of file
diff --git a/src/conversations/res/values-pt/strings.xml b/src/conversations/res/values-pt/strings.xml
new file mode 100644
index 000000000..a6b3daec9
--- /dev/null
+++ b/src/conversations/res/values-pt/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/conversations/res/values-ro-rRO/strings.xml b/src/conversations/res/values-ro-rRO/strings.xml
new file mode 100644
index 000000000..90b8763a9
--- /dev/null
+++ b/src/conversations/res/values-ro-rRO/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Alegeți-vă furnizorul XMPP
+ Folosește conversations.im
+ Creează un cont nou
+ Aveți deja un cont XMPP? S-ar putea să fie așa dacă deja utilizați un alt client XMPP sau dacă ați folosit Conversations în trecut. Dacă nu, puteți crea un cont nou XMPP chiar acum.\nIdee: Unii furnizori de e-mail oferă de asemenea și conturi XMPP.
+ XMPP este o rețea de mesagerie instant ce nu depinde de un anumit furnizor. Aveți posibilitatea să utilizați această aplicație cu orice server XMPP doriți.
+\nTotuși, pentru confortul dumneavoastră, am facilitat crearea unui cont pe conversations.im; un furnizor potrivit pentru utilizarea cu aplicația Conversations.
+ Ați fost invitați la %1$s. Vă vom ghida prin procesul de creare al unui cont.\nCând alegeți %1$s ca furnizor veți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP.
+ Ați fost invitați la %1$s. Un nume de utilizator a fost deja ales pentru dumneavoastră. Vă vom ghida prin procesul de creare al unui cont.\nVeți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP.
+ Invitația serverului dumneavoastră
+ Cod de acces formatat necorespunzător
+ Atingeți butonul de partajare pentru a trimite contactului o invitație la %1$s.
+ Dacă e în apropiere, contactul poate scana codul de mai jos pentru a vă accepta invitația.
+ Alătură-te %1$s și discută cu mine: %2$s
+ Partajează invitația cu…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-ru/strings.xml b/src/conversations/res/values-ru/strings.xml
new file mode 100644
index 000000000..a62334be8
--- /dev/null
+++ b/src/conversations/res/values-ru/strings.xml
@@ -0,0 +1,20 @@
+
+
+ Выберите своего XMPP-провайдера
+ Использовать conversations.im
+ Создать новый аккаунт
+ У вас есть аккаунт XMPP\? Если вы использовали Conversations или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас.
+\nПодсказка: Некоторые провайдеры электронной почты также регистрируют аккаунты XMPP.
+ XMPP - это независимая сеть обмена сообщениями. Это приложение позволяет подключиться к любому XMPP-серверу на ваш выбор.
+\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на conversations.im, сервере, специально предназначенном для работы с Conversations.
+ Вас пригласили на %1$s. Мы проведём вас через процесс создания аккаунта.
+\nАккаунт на %1$s позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.
+ Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта.
+\nЭтот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.
+ Ваше приглашение
+ Неправильный формат кода
+ Нажмите кнопку «Поделиться», чтобы отправить вашему контакту приглашение в %1$s.
+ Если ваш контакт находится поблизости, он также может отсканировать приведенный ниже код, чтобы принять ваше приглашение.
+ Присоединяйтесь к %1$s и пообщайтесь со мной: %2$s
+ Поделиться приглашением с…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-sk/strings.xml b/src/conversations/res/values-sk/strings.xml
new file mode 100644
index 000000000..58d638e37
--- /dev/null
+++ b/src/conversations/res/values-sk/strings.xml
@@ -0,0 +1,15 @@
+
+
+ Vyberte si svojho XMPP poskytovateľa
+ Použiť conversations.im
+ Vytvoriť nové konto
+ Máte už svoje XMPP konto? Môže to tak byť v prípade, že už používate iného klienta XMPP alebo ste predtým používali Conversations. Ak nie, môžete si vytvoriť nové XMPP konto práve teraz.\nHint: Niektorí poskytovatelia emailu zároveň poskytujú aj XMPP kontá.
+ XMPP je sieť pre okamžité správy nezávislá od poskytovateľa. Tohto klienta môžete používať s akýmkoľvek XMPP serverom, ktorý si vyberiete..
+\nAvšak pre vaše pohodlie sme zjednodušili vytvorenie konta na conversations.im; poskytovateľ špeciálne vhodný na používanie s Conversations.
+ Boli ste pozvaný do %1$s. Prevedieme vás procesom vytvorenia konta..\nPo výbere %1$s ako poskytovateľa, budete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu.
+ Boli ste pozvaný do %1$s . Užívateľské meno vám už bolo vopred vybrané. Prevedieme vás procesom vytvorenia konta..\nBudete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu.
+ Ťuknite na tlačidlo zdieľať na odoslanie pozvánky do %1$s vášmu kontaktu.
+ Ak je váš kontakt blízko, na prijatie vašej pozvánky si môže nasnímať kód nižšie.
+ Pripojte sa k %1$sa rozprávajte sa so mnou: %2$s
+ Zdieľať pozvánku s…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-sq/strings.xml b/src/conversations/res/values-sq/strings.xml
new file mode 100644
index 000000000..b87d55c82
--- /dev/null
+++ b/src/conversations/res/values-sq/strings.xml
@@ -0,0 +1,20 @@
+
+
+ XMPP është një rrjet shkëmbimi mesazhesh të atypëratyshëm i pavarur nga shërbimet. Këtë aplikacion mund ta përdorni me cilindo shërbyes XMPP që zgjidhni.
+\nMegjithatë, për lehtësi, e kemi bërë të kollajshme të krijohet një llogari te conversations.im, një shërbim posaçërisht i përshtatshëm për përdorim me Conversations.
+ Jeni ftuar te %1$s. Do t’ju udhëheqim përmes procesit të krijimit të një llogarie.
+\nKur zgjidhet %1$s si shërbim, do të jeni në gjendje të komunikoni me përdorues nga shërbime të tjera duke u dhënë adresën tuaj të plotë XMPP.
+ Jeni ftuar te %1$s. Për ju është zgjedhur tashmë një emër përdoruesi. Do t’ju udhëheqim përmes procesit të krijimit të një llogarie.
+\nDo të jeni në gjendje të komunikoni me përdorues nga shërbime të tjera duke u dhënë adresën tuaj të plotë XMPP.
+ Prekni butonin e ndarjes me të tjerë që t’i dërgoni kontaktit tuaj një ftesë për te %1$s.
+ Nëse kontakti juaj është atypari, mund të skanojë gjithashtu kodin më poshtë, që të pranojë ftesën tuaj.
+ Bëhuni pjesë e %1$s dhe bisedoni me: %2$s
+ Ndajeni ftesën me…
+ Krijoni llogari të re
+ Zgjidhni shërbimin tuaj XMPP
+ Përdor conversations.im
+ Keni tashmë një llogari XMPP\? Mund të jetë kështu nëse përdorni tashmë një klient tjetër XMPP, ose e keni përdorur Conversations më parë. Nëse jo, mund të krijoni një llogari të re XMPP që tani.
+\nNdihmëz: Disa shërbime email-i ofrojnë gjithashtu llogari XMPP.
+ Ftesë nga shërbyesi juaj
+ Kod i formatuar jo saktësisht
+
\ No newline at end of file
diff --git a/src/conversations/res/values-sr/strings.xml b/src/conversations/res/values-sr/strings.xml
new file mode 100644
index 000000000..3bbef8725
--- /dev/null
+++ b/src/conversations/res/values-sr/strings.xml
@@ -0,0 +1,10 @@
+
+
+ Одаберите вашег ИксМПП провајдера
+ Користи conversations.im
+ Направи нови налог
+ Да ли већ имате ИксМПП налог? Извесно је да га имате ако користите неки ИксМПП клијент или сте раније користили Конверзацију. Ако немате, сада можете направити нови ИксМПП налог.\nСавет: неки поштански провајдери такође омогућавају и ИксМПП налоге.
+ ИксМПП је мрежа брзих порука, независна од провајдера. Овај клијент можете користити уз било који сервер по вашем избору.
+\nДа бисмо вам олакшали, омогућили смо креирање налога на conversations.im; провајдеру специјално прилаг.ођеном за коришћење уз Конверзацију
+ Ваша серверска позивница
+
\ No newline at end of file
diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml
new file mode 100644
index 000000000..062a0c26f
--- /dev/null
+++ b/src/conversations/res/values-sv/strings.xml
@@ -0,0 +1,19 @@
+
+
+ Välj din XMPP-leverantör
+ Använd conversations.im
+ Skapa ett nytt konto
+ Har du redan ett XMPP-konto? Detta kan vara fallet om du redan använder en annan XMPP-klient eller om du har använt Conversations tidigare. Om inte, kan du skapa ett nytt XMPP-konto på en gång.\nTips: Vissa e-postleverantörer tillhandahåller även XMPP-konton.
+ Din serverinbjudan
+ Felaktigt formaterad provisioneringskod
+ Tryck på dela-knappen för att skicka en inbjudan till din kontakt till %1$s.
+ Om din kontakt är i närheten, kan de också skanna koden nedan för att acceptera din inbjudan.
+ Gå med %1$s och chatta med mig: %2$s
+ Dela inbjudan med…
+ Du har blivit inbjuden till %1$s. Ett användarnamn har redan valts åt dig. Vi guidar dig genom processen för att skapa ett konto.
+\nDu kommer att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress.
+ XMPP är ett leverantörsoberoende snabbmeddelandenätverk. Du kan använda den här klienten med vilken XMPP-server du än väljer.
+\nMen för din bekvämlighet har vi gjort det enkelt att skapa ett konto på conversations.im; en leverantör som är speciellt lämpad för användning med Conversations.
+ Du har blivit inbjuden till %1$s. Vi guidar dig genom processen för att skapa ett konto.
+\nNär du väljer %1$s som leverantör kommer du att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress.
+
\ No newline at end of file
diff --git a/src/conversations/res/values-szl/strings.xml b/src/conversations/res/values-szl/strings.xml
new file mode 100644
index 000000000..bc26248a5
--- /dev/null
+++ b/src/conversations/res/values-szl/strings.xml
@@ -0,0 +1,17 @@
+
+
+ Wybier liferanta XMPP
+ Użyj conversations.im
+ Stwōrz nowe kōnto
+ Mosz już kōnto XMPP? Tak może być, jeźli już używosz inkszego klijynta XMPP aboś używoł abo używała wcześnij Conversations. Jak niy, to możesz stworzić teroz nowe kōnto XMPP.\nDorada: Niykerzi liferańcio emaili dowajōm tyż kōnta XMPP.
+ XMPP to je nec wartkich wiadōmości niyzależny ôd liferanta. Możesz używać tego klijynta ze serwerym XMPP, jaki sie wybieresz.
+\nAle dlo twojij wygody ułacniyli my tworzynie kōnt na conversations.im; liferańcie ekstra dopasowanym do używanio ze Conversations.
+ Mosz zaproszynie na %1$s. Pokludzymy cie bez proces tworzynio kōnta.\nPo wybraniu %1$s za liferanta, poradzisz kōmunikować sie ze używoczami ôd inkszych liferantōw bez danie im swojij połnyj adresy XMPP.
+ Mosz zaproszynie na %1$s. Miano ôd używocza już je do ciebie wybrane. Pokludzymy cie bez proces tworzynio kōnta.\nBydzie szło kōmunikować sie ze używoczami ôd inkszych liferantōw bez danie im swojij połnyj adresy XMPP.
+ Twoje zaproszynie na serwer
+ Niynoleżnie sformatowany kod lifrowanio
+ Tyknij knefla dzielynio sie, żeby posłać kōntaktowi zaproszynie na %1$s.
+ Jeźli kōntakt je blisko, to może tyż zeskanować kod niżyj, żeby zaakceptować twoje zaproszynie.
+ Pōdź na %1$s i pogodej zy mnōm: %2$s
+ Poślij zaproszynie do…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-tr-rTR/strings.xml b/src/conversations/res/values-tr-rTR/strings.xml
new file mode 100644
index 000000000..446c5485a
--- /dev/null
+++ b/src/conversations/res/values-tr-rTR/strings.xml
@@ -0,0 +1,19 @@
+
+
+ XMPP sağlayıcınızı seçin
+ conversations.im kullan
+ Yeni hesap oluştur
+ Zaten bir XMPP hesabınız var mı? Bunun sebebi, zaten başka bir XMPP istemcisi kullanıyor oluşunuz veya Conversations\'ı önceden kullanmış olmanız olabilir. Eğer yoksa şimdi yeni bir XMPP hesabı oluşturabilirsiniz.
+\nİpucu: Bazı e-posta sağlayıcıları da XMPP hesapları sağlayabilir.
+ XMPP, sağlayıcıdan bağımsız bir anlık mesajlaşma ağıdır. Bu uygulamayı seçtiğiniz herhangi bir sağlayıcıyla kullanabilirsiniz.
+\nFakat, kullanım kolaylığı için, özellikle Conversations ile kullanılmak için tasarlanmış olan conversations.im sunucusunu da kullanabilirsiniz.
+ %1$s sağlayıcısına davet edildiniz. Sizi hesap oluşturulması konusunda yönlendireceğiz.
+\n%1$s bir sağlayıcı olarak seçildiğinde, başka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz.
+ %1$s sağlayıcısına davet edildiniz. Sizin için zaten bir kullanıcı adı seçildi. Sizi hesap oluşturulması konusunda yönlendireceğiz.\nBaşka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz.
+ Sunucu davetiyeniz
+ Yanlış ayarlanmış düzenleme kodu
+ Kişiyi %1$s\'e davet etmek için paylaş butonuna dokunun.
+ Kişiniz yakınınızda ise, aşağıdaki kodu tarayak daveti kabul edebilirler.
+ %1$s grubuna katıl ve benimle sohbet et: %2$s
+ Daveti şununla paylaş…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-uk/strings.xml b/src/conversations/res/values-uk/strings.xml
new file mode 100644
index 000000000..f9e37cea7
--- /dev/null
+++ b/src/conversations/res/values-uk/strings.xml
@@ -0,0 +1,20 @@
+
+
+ Виберіть постачальника послуг обміну повідомленнями XMPP
+ Скористатися conversations.im
+ Створити новий обліковий запис
+ Уже маєте обліковий запис XMPP\? Можливо, користуєтеся іншою програмою XMPP або користувалися Conversations раніше. Якщо ні, можете створити новий обліковий запис XMPP просто зараз.
+\nЗверніть увагу, що деякі постачальники електронної пошти у той же час надають облікові записи XMPP.
+ XMPP — це мережа обміну повідомленнями, незалежна від постачальників. Можете використовувати цю програму з будь-яким XMPP-сервером, який оберете.
+\nПроте для зручності ми спростили створення облікового запису на conversations.im — у постачальника, спеціально налаштованого на роботу з Conversations.
+ Вас запросили до %1$s. Ми проведемо Вас крок за кроком, щоб створити обліковий запис.
+\nОбравши %1$s в якості свого постачальника, Ви зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP.
+ Вас запросили до %1$s. Для Вас створено ім\'я користувача. Ми проведемо Вас крок за кроком, щоб створити обліковий запис.
+\nВи зможете спілкуватися з користувачами інших постачальників, для цього повідомте їм свою повну адресу XMPP.
+ Ваше запрошення до сервера
+ Неправильно відформатований код забезпечення
+ Якщо контакт поблизу, він також може прийняти запрошення, відсканувавши код нижче.
+ Приєднуйтеся до %1$s і спілкуйтеся зі мною: %2$s
+ Запросити…
+ Натисніть «Поділитися», щоб надіслати Вашому контакту запрошення до %1$s.
+
\ No newline at end of file
diff --git a/src/conversations/res/values-vi/strings.xml b/src/conversations/res/values-vi/strings.xml
new file mode 100644
index 000000000..851ad0927
--- /dev/null
+++ b/src/conversations/res/values-vi/strings.xml
@@ -0,0 +1,20 @@
+
+
+ Chọn nhà cung cấp XMPP của bạn
+ Sử dụng “conversations.im”
+ Tạo tài khoản mới
+ Bạn đã có sẵn một tài khoản XMPP chưa\? Nếu bạn đang dùng một ứng dụng XMPP khác dành cho máy khách (client) hoặc đã sử dụng Conversations trước đó. Nếu chưa có, bạn có thể tạo tài khoản XMPP mới ngay bây giờ.
+\nGợi ý: Một số nhà cung cấp dịch vụ email cũng cung cấp tài khoản XMPP.
+ XMPP là một dịch vụ mạng tin nhắn không phụ thuộc vào nhà cung cấp nào. Bạn có thể sử dụng ứng dụng máy khách này với bất kỳ máy chủ XMPP nào mà bạn chọn.
+\nĐể thuận tiện hơn cho bạn, chúng tôi đã đơn giản hóa khâu tạo tài khoản trên conversations.im – một nhà cung cấp đặc biệt phù hợp cho việc sử dụng Conversations.
+ Bạn đã được mời vào “ %1$s”. Chúng tôi sẽ hướng dẫn bạn xuyên suốt quá trình tạo tài khoản.
+\nKhi chọn “%1$s” làm nhà cung cấp, bạn sẽ có thể liên lạc với những người dùng của các nhà cung cấp khác bằng cách đưa cho họ địa chỉ XMPP đầy đủ của bạn.
+ Bạn đã được mời vào “%1$s.” Một tên người dùng đã được chọn sẵn cho bạn. Chúng tôi sẽ hướng dẫn bạn xuyên suốt quá trình tạo tài khoản.
+\nBạn sẽ có thể với những người dùng của các nhà cung cấp khác bằng cách đưa cho họ địa chỉ XMPP đầy đủ của bạn.
+ Lời mời vào máy chủ của bạn
+ Mã cung cấp sai định dạng
+ Nhấn nút chia sẻ để gửi đến liên hệ của bạn một lời mời vào “%1$s”.
+ Nếu liên hệ của bạn đang ở gần bên bạn, họ có thể quét mã ở dưới để chấp nhận lời mời của bạn.
+ Hãy tham gia vào “%1$s” và trò chuyện với tôi: %2$s
+ Chia sẻ lời mời với…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml
new file mode 100644
index 000000000..b16b819f7
--- /dev/null
+++ b/src/conversations/res/values-zh-rCN/strings.xml
@@ -0,0 +1,20 @@
+
+
+ 选择您的 XMPP 提供者
+ 使用 conversations.im
+ 创建新账号
+ 您已经有 XMPP 账号了吗?如果您之前使用过 Conversations 或其他 XMPP 客户端,那么您已经有账号了。如果没有,您可以立即创建一个。
+\n提示:一些电子邮件服务也提供 XMPP 账号。
+ XMPP 是独立于提供者的即时通讯网络。您选择的任何 XMPP 服务器都可以使用此应用。
+\n不过,您可以轻松地在 conversations.im 上创建账号;特别适合与 Conversations 使用的提供者。
+ 您已受邀加入 %1$s。我们将指导您创建账号。
+\n当选择 %1$s 作为提供者时,向其他 XMPP 用户提供您的完整地址,就能和对方交流。
+ 您已受邀加入 %1$s。已为您选择了用户名。我们将指导您创建账号。
+\n向其他 XMPP 用户提供您的完整地址,就能和对方交流。
+ 您的服务器邀请
+ 配置代码格式不正确
+ 轻击分享按钮,向您的联系人发送加入 %1$s 的邀请。
+ 如果您的联系人在附近,对方也可以扫描下方二维码接受邀请。
+ 加入 %1$s 和我聊天:%2$s
+ 分享邀请至…
+
\ No newline at end of file
diff --git a/src/conversations/res/values-zh-rTW/strings.xml b/src/conversations/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..a356693d8
--- /dev/null
+++ b/src/conversations/res/values-zh-rTW/strings.xml
@@ -0,0 +1,20 @@
+
+
+ 挑選您的 XMPP 提供者
+ 使用 conversations.im
+ 建立新帳戶
+ 您已經擁有一個 XMPP 帳戶了嗎?如果您之前使用過其他 XMPP 用戶端或 Conversations 的話,那麼您已經擁有 XMPP 帳戶了。若沒有,您現在就建立一個新的 XMPP 帳戶。
+\n提示:部分電子郵件提供者也會提供 XMPP 帳戶。
+ XMPP 是獨立於服務提供者的即時訊息網路。任何您選擇的 XMPP 伺服器都可在此用戶端上使用。
+\n不過,我們令它在 Coversations.im 中建立帳戶變得更方便;conversations.im 是特別適合 Conversations 的提供者。
+ 你已受邀參加 %1$s 。我們將指引您完成建立帳戶的過程。
+\n選擇 %1$s 作為提供者後,您可以將您完整的 XMPP 位址交給使用其他提供者的使用者,以便能與他們進行交流。
+ 你已受邀參加 %1$s 。我們已經為您挑選了一個使用者名稱。我們將指引您完成建立帳戶的過程。
+\n您可以將您完整的 XMPP 位址交給使用其他提供者的使用者,以便能與他們進行交流。
+ 您的伺服器邀請
+ 佈建代碼格式不正確
+ 輕觸分享按鍵以向您的聯絡人傳送加入 %1$s 的邀請。
+ 如果您的聯絡人就在附近,他們也可以掃描下面的代碼以接受您的邀請。
+ 加入 %1$s 與我聊天:%2$s
+ 分享邀請至…
+
\ No newline at end of file
diff --git a/src/conversations/res/values/colors-themed.xml b/src/conversations/res/values/colors-themed.xml
new file mode 100644
index 000000000..b9369b2e9
--- /dev/null
+++ b/src/conversations/res/values/colors-themed.xml
@@ -0,0 +1,63 @@
+
+
+ #006E1C
+ #FFFFFF
+ #98F994
+ #002204
+ #52634F
+ #FFFFFF
+ #D5E8CF
+ #111F0F
+ #38656A
+ #FFFFFF
+ #BCEBF0
+ #002023
+ #BA1A1A
+ #FFDAD6
+ #FFFFFF
+ #410002
+ #FCFDF6
+ #1A1C19
+ #FCFDF6
+ #1A1C19
+ #DEE5D8
+ #424940
+ #72796F
+ #F0F1EB
+ #2F312D
+ #7DDC7A
+ #000000
+ #006E1C
+ #C2C9BD
+ #000000
+ #7DDC7A
+ #00390A
+ #005313
+ #98F994
+ #BACCB3
+ #253423
+ #3B4B38
+ #D5E8CF
+ #A0CFD4
+ #00363B
+ #1F4D52
+ #BCEBF0
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #1A1C19
+ #E2E3DD
+ #1A1C19
+ #E2E3DD
+ #424940
+ #C2C9BD
+ #8C9388
+ #1A1C19
+ #E2E3DD
+ #006E1C
+ #000000
+ #7DDC7A
+ #424940
+ #000000
+
diff --git a/src/conversations/res/values/strings.xml b/src/conversations/res/values/strings.xml
new file mode 100644
index 000000000..715b40f6c
--- /dev/null
+++ b/src/conversations/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+
+ Pick your Jabber service
+ Use ChatterboxTown
+ Create new account
+ Do you already have a Jabber ID? This might be the case if you are already using a different Jabber client or have used monocles chat, Conversations, or Snikket before. If not you can create a new Jabber ID.
+ Jabber is a provider independent instant messaging network. You can use this client with what ever Jabber service you choose.
+ You have been invited to %1$s. We will guide you through the process of creating an account.\nWhen picking %1$s as a provider you will be able to communicate with users of other providers by giving them your full Jabber ID.
+ You have been invited to %1$s. A username has already been picked for you. We will guide you through the process of creating an account.\nYou will be able to communicate with users of other providers by giving them your full Jabber ID.
+ Your server invitation
+ Improperly formatted provisioning code
+ Tap the share button to send your contact an invitation to %1$s.
+ If your contact is nearby, they can also scan the code below to accept your invitation.
+ Join %1$s and chat with me: %2$s
+ Share invite with…
+
diff --git a/src/conversationsFree/java/eu/siacs/conversations/utils/InstallReferrerUtils.java b/src/conversationsFree/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
new file mode 100644
index 000000000..3aa49798d
--- /dev/null
+++ b/src/conversationsFree/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
@@ -0,0 +1,15 @@
+package eu.siacs.conversations.utils;
+
+import eu.siacs.conversations.ui.MagicCreateActivity;
+import eu.siacs.conversations.ui.WelcomeActivity;
+
+public class InstallReferrerUtils {
+
+ public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
+
+ }
+
+ public static void markInstallReferrerExecuted(MagicCreateActivity magicCreateActivity) {
+ //stub
+ }
+}
\ No newline at end of file
diff --git a/src/conversationsPlaystore/java/de/monocles/chat/CheogramLicenseChecker.java b/src/conversationsPlaystore/java/de/monocles/chat/CheogramLicenseChecker.java
new file mode 100644
index 000000000..e136b27ea
--- /dev/null
+++ b/src/conversationsPlaystore/java/de/monocles/chat/CheogramLicenseChecker.java
@@ -0,0 +1,45 @@
+package de.monocles.chat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings.Secure;
+import android.util.Log;
+
+import com.google.android.vending.licensing.*;
+
+import eu.siacs.conversations.utils.BiConsumer;
+
+import eu.siacs.conversations.R;
+
+public class CheogramLicenseChecker implements LicenseCheckerCallback {
+ private final LicenseChecker mChecker;
+ private final BiConsumer mCallback;
+
+ public CheogramLicenseChecker(Context context, BiConsumer callback) {
+ mChecker = new LicenseChecker(context, new StrictPolicy(), context.getResources().getString(R.string.licensePublicKey));
+ mCallback = callback;
+ }
+
+ public void checkLicense() {
+ mChecker.checkAccess(this);
+ }
+
+ @Override
+ public void dontAllow(int reason) {
+ Log.d("CheogramLicenseChecker", "dontAllow: " + reason);
+ mCallback.accept(null, null);
+ }
+
+ @Override
+ public void applicationError(int errorCode) {
+ Log.d("CheogramLicenseChecker", "applicationError: " + errorCode);
+ mCallback.accept(null, null);
+ }
+
+ @Override
+ public void allow(int reason, ResponseData data, String signedData, String signature) {
+ Log.d("CheogramLicenseChecker", "" + reason + " / " + data + " / " + signedData + " / " + signature);
+ mCallback.accept(signedData, signature);
+ }
+}
diff --git a/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java b/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
new file mode 100644
index 000000000..360011bea
--- /dev/null
+++ b/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
@@ -0,0 +1,70 @@
+package eu.siacs.conversations.utils;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import com.android.installreferrer.api.InstallReferrerClient;
+import com.android.installreferrer.api.InstallReferrerStateListener;
+import com.android.installreferrer.api.ReferrerDetails;
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.ui.WelcomeActivity;
+
+public class InstallReferrerUtils implements InstallReferrerStateListener {
+
+ private static final String PROCESSED_INSTALL_REFERRER = "processed_install_referrer";
+
+
+ private final WelcomeActivity welcomeActivity;
+ private final InstallReferrerClient installReferrerClient;
+
+
+ public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
+ this.welcomeActivity = welcomeActivity;
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(welcomeActivity);
+ if (preferences.getBoolean(PROCESSED_INSTALL_REFERRER, false)) {
+ Log.d(Config.LOGTAG, "install referrer already processed");
+ this.installReferrerClient = null;
+ return;
+ }
+ this.installReferrerClient = InstallReferrerClient.newBuilder(welcomeActivity).build();
+ try {
+ this.installReferrerClient.startConnection(this);
+ } catch (SecurityException e) {
+ Log.e(Config.LOGTAG, "unable to start connection to InstallReferrerClient", e);
+ }
+ }
+
+ public static void markInstallReferrerExecuted(final Activity context) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ preferences.edit().putBoolean(PROCESSED_INSTALL_REFERRER, true).apply();
+ }
+
+ @Override
+ public void onInstallReferrerSetupFinished(int responseCode) {
+ if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
+ try {
+ final ReferrerDetails referrerDetails = installReferrerClient.getInstallReferrer();
+ final String referrer = referrerDetails.getInstallReferrer();
+ if (Strings.isNullOrEmpty(referrer)) {
+ return;
+ }
+ welcomeActivity.onInstallReferrerDiscovered(Uri.parse(referrer));
+ } catch (final RemoteException | IllegalArgumentException e) {
+ Log.d(Config.LOGTAG, "unable to get install referrer", e);
+ }
+ } else {
+ Log.d(Config.LOGTAG, "unable to setup install referrer client. code=" + responseCode);
+ }
+ }
+
+ @Override
+ public void onInstallReferrerServiceDisconnected() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/conversationsPlaystore/res/values/strings.xml b/src/conversationsPlaystore/res/values/strings.xml
new file mode 100644
index 000000000..103fe6979
--- /dev/null
+++ b/src/conversationsPlaystore/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ de.monocles.chat.playstore
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlcl4jV0uqcCD9EX8RS2FuxtkZIrAyQhBHFWWaYXH+Fdh2KO6bykD0sabYxAtKDquSagkQKgiDsj7lKdk01tI+3prGI1D96ml7bzRmGt82PltR1e7sE9FTfhzn2+6zWOcsabZEgCZRBBIY2TUZtWlagJE7aJncS/sERjdaiRkDqDnCqsBjLvRcfEEnc3KJjhQicv4GTau5wJnXWQcgz5FPe2j0sIQjbzHrSHn0omKV9ObwxDYn5JPfBV/0nePLUFrkONrRsNy7311aVJOj4Hqd4y74vRjdBV9hI+PLJ9R+y4p1nhV1F3Ih/VAsw5pDECpe/sKput/lpIsxnn/W3DKvQIDAQAB
+
diff --git a/src/debug/logo_actionbar_white-playstore.png b/src/debug/logo_actionbar_white-playstore.png
deleted file mode 100644
index f53598c41..000000000
Binary files a/src/debug/logo_actionbar_white-playstore.png and /dev/null differ
diff --git a/src/debug/res/values/logo_actionbar_white_background.xml b/src/debug/res/values/logo_actionbar_white_background.xml
deleted file mode 100644
index d6bf1183e..000000000
--- a/src/debug/res/values/logo_actionbar_white_background.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #3DDC84
-
\ No newline at end of file
diff --git a/src/free/AndroidManifest.xml b/src/free/AndroidManifest.xml
new file mode 100644
index 000000000..f855bbccf
--- /dev/null
+++ b/src/free/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/free/java/de/monocles/chat/CheogramLicenseChecker.java b/src/free/java/de/monocles/chat/CheogramLicenseChecker.java
new file mode 100644
index 000000000..6c3d83a70
--- /dev/null
+++ b/src/free/java/de/monocles/chat/CheogramLicenseChecker.java
@@ -0,0 +1,19 @@
+package de.monocles.chat;
+
+import android.content.Context;
+import android.util.Log;
+
+import eu.siacs.conversations.utils.BiConsumer;
+
+public class CheogramLicenseChecker {
+ private BiConsumer mCallback;
+
+ public CheogramLicenseChecker(Context context, BiConsumer callback) {
+ mCallback = callback;
+ }
+
+ public void checkLicense() {
+ Log.d("CheogramLicenseChecker", "skipping license checks in free build");
+ mCallback.accept(null, null);
+ }
+}
diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java
new file mode 100644
index 000000000..f436da434
--- /dev/null
+++ b/src/free/java/eu/siacs/conversations/services/PushManagementService.java
@@ -0,0 +1,33 @@
+package eu.siacs.conversations.services;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Conversation;
+
+public class PushManagementService {
+
+ protected final XmppConnectionService mXmppConnectionService;
+
+ public PushManagementService(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ void registerPushTokenOnServer(Account account) {
+ //stub implementation. only affects playstore flavor
+ }
+
+ void unregisterChannel(Account account, String hash) {
+ //stub implementation. only affects playstore flavor
+ }
+
+ public boolean available(Account account) {
+ return false;
+ }
+
+ public boolean isStub() {
+ return true;
+ }
+
+ public boolean availableAndUseful(Account account) {
+ return false;
+ }
+}
diff --git a/src/git/AndroidManifest.xml b/src/git/AndroidManifest.xml
deleted file mode 100644
index eb179391d..000000000
--- a/src/git/AndroidManifest.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/git/java/de/monocles/chat/ui/StartUI.java b/src/git/java/de/monocles/chat/ui/StartUI.java
deleted file mode 100644
index b7686b1d0..000000000
--- a/src/git/java/de/monocles/chat/ui/StartUI.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package de.monocles.chat.ui;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.ui.ConversationsActivity;
-import eu.siacs.conversations.ui.util.IntroHelper;
-public class StartUI extends AppCompatActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_start_ui);
- IntroHelper.showIntro(this, false);
- String PREF_FIRST_START = "FirstStart";
- SharedPreferences FirstStart = getApplicationContext().getSharedPreferences(PREF_FIRST_START, Context.MODE_PRIVATE);
- long FirstStartTime = FirstStart.getLong(PREF_FIRST_START, 0);
- Log.d(Config.LOGTAG, "Starting " + getString(R.string.app_name) + "(" + FirstStartTime + ")");
- Intent intent = new Intent(this, ConversationsActivity.class);
- intent.putExtra(PREF_FIRST_START, FirstStartTime);
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- finish(); }
-
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- }
-}
diff --git a/src/git/java/eu/siacs/conversations/services/EmojiInitializationService.java b/src/git/java/eu/siacs/conversations/services/EmojiInitializationService.java
deleted file mode 100644
index e16afc3d1..000000000
--- a/src/git/java/eu/siacs/conversations/services/EmojiInitializationService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package eu.siacs.conversations.services;
-
-import android.content.Context;
-
-import androidx.emoji2.bundled.BundledEmojiCompatConfig;
-import androidx.emoji2.text.EmojiCompat;
-
-public class EmojiInitializationService {
-
- public static void execute(final Context context) {
- EmojiCompat.init(new BundledEmojiCompatConfig(context).setReplaceAll(true));
- }
-
-}
\ No newline at end of file
diff --git a/src/git/java/eu/siacs/conversations/services/PushManagementService.java b/src/git/java/eu/siacs/conversations/services/PushManagementService.java
deleted file mode 100644
index 2c7345fc6..000000000
--- a/src/git/java/eu/siacs/conversations/services/PushManagementService.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package eu.siacs.conversations.services;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Conversation;
-
-public class PushManagementService {
-
- protected final XmppConnectionService mXmppConnectionService;
-
- public PushManagementService(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- void registerPushTokenOnServer(Account account) {
- //stub implementation. only affects playstore flavor
- }
-
- void registerPushTokenOnServer(Conversation conversation) {
- //stub implementation. only affects playstore flavor
- }
-
- void unregisterChannel(Account account, String hash) {
- //stub implementation. only affects playstore flavor
- }
-
- public boolean available(Account account) {
- return false;
- }
-
- public boolean isStub() {
- return true;
- }
-}
diff --git a/src/git/java/eu/siacs/conversations/utils/InstallReferrerUtils.java b/src/git/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
deleted file mode 100644
index 325caa8d0..000000000
--- a/src/git/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import eu.siacs.conversations.ui.MagicCreateActivity;
-import eu.siacs.conversations.ui.WelcomeActivity;
-
-public class InstallReferrerUtils {
-
- public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
- }
-
- public static void markInstallReferrerExecuted(MagicCreateActivity magicCreateActivity) {
- }
-}
\ No newline at end of file
diff --git a/src/git/java/eu/siacs/conversations/utils/StorageHelper.java b/src/git/java/eu/siacs/conversations/utils/StorageHelper.java
deleted file mode 100644
index a29e1348d..000000000
--- a/src/git/java/eu/siacs/conversations/utils/StorageHelper.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import static eu.siacs.conversations.persistance.FileBackend.APP_DIRECTORY;
-import static eu.siacs.conversations.persistance.FileBackend.STORAGE_INDEX;
-
-import android.content.Context;
-import android.os.Environment;
-
-import androidx.annotation.Nullable;
-
-import java.io.File;
-
-public class StorageHelper {
-
- public static File getConversationsDirectory(final Context context, final String type) {
- if (type.equalsIgnoreCase("null")) {
- return new File(getStorage(context, STORAGE_INDEX.get(), APP_DIRECTORY, type));
- } else {
- return new File(getAppMediaDirectory(context, type) + APP_DIRECTORY + " " + type);
- }
- }
-
- public static String getAppMediaDirectory(final Context context, final String type) {
- return getStorage(context, STORAGE_INDEX.get(), APP_DIRECTORY, type);
- }
-
- public static String getStorage(Context c, int index, String APP_DIRECTORY, String type) {
- if (index == 0) {
- return getData(c, APP_DIRECTORY, type);
- } else {
- return c.getFilesDir().getAbsolutePath() + File.separator;
- }
- }
-
- public static String getBackupDirectory(@Nullable String app) {
- if (app != null && (app.equalsIgnoreCase("conversations") || app.equalsIgnoreCase("Quicksy"))) {
- return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + app + File.separator + "Backup" + File.separator;
- } else if (app != null && (app.equalsIgnoreCase("Monocles Messenger"))) {
- return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + app + File.separator + "Database" + File.separator;
- } else {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "Database" + File.separator;
- }
- }
-
- private static String getData(Context context, String APP_DIRECTORY, String type) {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "Media" + File.separator;
- }
-
- public static String getGlobalPicturesPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "pictures" + File.separator;
- }
-
- public static String getGlobalVideosPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "videos" + File.separator;
- }
-
- public static String getGlobalDocumentsPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "documents" + File.separator;
- }
-
- public static String getGlobalDownloadsPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "downloads" + File.separator;
- }
-
- public static String getGlobalAudiosPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "audios" + File.separator;
- }
-
- public static String getAppUpdateDirectory() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "Update" + File.separator;
- }
-
- public static String getAppLogsDirectory() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + File.separator + APP_DIRECTORY + File.separator + "Chats" + File.separator;
- }
-
- public static void migrateStorage(Context c) {
- //ignore
- }
-}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index dbea0b5e6..a0eb77b17 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -2,58 +2,20 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -63,38 +25,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -111,43 +78,28 @@
-
+
-
-
-
-
-
-
-
-
-
@@ -164,9 +116,9 @@
+ android:name="androidx.work.impl.foreground.SystemForegroundService"
+ android:foregroundServiceType="dataSync"
+ tools:node="merge" />
-
+
-
+
+
@@ -200,22 +158,10 @@
-
-
-
-
-
-
-
-
@@ -227,21 +173,31 @@
-
+
-
-
-
+
+
+ android:theme="@style/Theme.Conversations3.Dialog" />
+
+
@@ -250,17 +206,16 @@
+ android:launchMode="singleTask"
+ android:minWidth="300dp"
+ android:minHeight="300dp"
+ android:windowSoftInputMode="stateHidden" />
-
-
-
+
+
+
-
@@ -323,71 +277,17 @@
+ android:exported="true"
+ android:label="@string/title_activity_new_chat"
+ android:launchMode="singleTop">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:name=".ui.activity.SettingsActivity"
+ android:exported="true"
+ android:label="@string/title_activity_settings">
@@ -395,21 +295,21 @@
+ android:label="@string/title_activity_choose_contact"
+ android:exported="false" />
+ android:label="@string/title_activity_block_list"
+ android:exported="false" />
+ android:label="@string/change_password_on_server"
+ android:exported="false" />
+ android:exported="true"
+ android:label="@string/choose_account">
@@ -417,11 +317,6 @@
-
-
-
+ android:label="@string/group_chat_avatar"
+ android:exported="false" />
+ android:exported="true"
+ android:launchMode="singleTop">
+
+
+
+
@@ -485,30 +373,6 @@
android:name="android.service.chooser.chooser_target_service"
android:value="eu.siacs.conversations.services.ContactChooserTargetService" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:parentActivityName=".ui.SettingsActivity">
+
-
+ android:theme="@style/Base.Theme.AppCompat" />
+
+
-
-
-
-
-
+ android:exported="false" />
+
+
+
+
+
+
+
+
diff --git a/src/main/assets/animate.min.css b/src/main/assets/animate.min.css
deleted file mode 100644
index b6f612953..000000000
--- a/src/main/assets/animate.min.css
+++ /dev/null
@@ -1,11 +0,0 @@
-@charset "UTF-8";
-
-/*!
- * animate.css -http://daneden.me/animate
- * Version - 3.5.1
- * Licensed under the MIT license - http://opensource.org/licenses/MIT
- *
- * Copyright (c) 2016 Daniel Eden
- */
-
-.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.hinge{-webkit-animation-duration:2s;animation-duration:2s}.animated.bounceIn,.animated.bounceOut,.animated.flipOutX,.animated.flipOutY{-webkit-animation-duration:.75s;animation-duration:.75s}@-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}40%,43%,70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06)}70%{-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}.headShake{-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-name:headShake;animation-name:headShake}@-webkit-keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.swing{-webkit-transform-origin:top center;transform-origin:top center;-webkit-animation-name:swing;animation-name:swing}@-webkit-keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}@keyframes wobble{0%{-webkit-transform:none;transform:none}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:none;transform:none}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}@keyframes jello{0%,11.1%,to{-webkit-transform:none;transform:none}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{-webkit-animation-name:jello;animation-name:jello;-webkit-transform-origin:center;transform-origin:center}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{opacity:1;-webkit-transform:scale3d(1.03,1.03,1.03);transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{opacity:1;-webkit-transform:scaleX(1);transform:scaleX(1)}}.bounceIn{-webkit-animation-name:bounceIn;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-3000px,0);transform:translate3d(0,-3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(-3000px,0,0);transform:translate3d(-3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(25px,0,0);transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}@keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(3000px,0,0);transform:translate3d(3000px,0,0)}60%{opacity:1;-webkit-transform:translate3d(-25px,0,0);transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:none;transform:none}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,3000px,0);transform:translate3d(0,3000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{opacity:1;-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}to{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-name:bounceOut;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;-webkit-transform:translate3d(20px,0,0);transform:translate3d(20px,0,0)}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{opacity:1;-webkit-transform:translate3d(-20px,0,0);transform:translate3d(-20px,0,0)}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInDownBig{0%{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInLeftBig{0%{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRight{0%{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInRightBig{0%{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes fadeInUpBig{0%{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}to{opacity:1;-webkit-transform:none;transform:none}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,2000px,0);transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-2000px,0,0);transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(2000px,0,0);transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}@keyframes flip{0%{-webkit-transform:perspective(400px) rotateY(-1turn);transform:perspective(400px) rotateY(-1turn)}0%,40%{-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}40%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-190deg);transform:perspective(400px) translateZ(150px) rotateY(-190deg)}50%{-webkit-transform:perspective(400px) translateZ(150px) rotateY(-170deg);transform:perspective(400px) translateZ(150px) rotateY(-170deg)}50%,80%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}80%{-webkit-transform:perspective(400px) scale3d(.95,.95,.95);transform:perspective(400px) scale3d(.95,.95,.95)}to{-webkit-transform:perspective(400px);transform:perspective(400px);-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}}.animated.flip{-webkit-backface-visibility:visible;backface-visibility:visible;-webkit-animation-name:flip;animation-name:flip}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);transform:perspective(400px) rotateX(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInX;animation-name:flipInX}@-webkit-keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}0%,40%{-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}40%{-webkit-transform:perspective(400px) rotateY(-20deg);transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);transform:perspective(400px) rotateY(10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipInY;animation-name:flipInY}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);transform:perspective(400px) rotateX(-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotateX(90deg);transform:perspective(400px) rotateX(90deg);opacity:0}}.flipOutX{-webkit-animation-name:flipOutX;animation-name:flipOutX;-webkit-backface-visibility:visible!important;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);transform:perspective(400px) rotateY(-15deg);opacity:1}to{-webkit-transform:perspective(400px) rotateY(90deg);transform:perspective(400px) rotateY(90deg);opacity:0}}.flipOutY{-webkit-backface-visibility:visible!important;backface-visibility:visible!important;-webkit-animation-name:flipOutY;animation-name:flipOutY}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);transform:translate3d(100%,0,0) skewX(-30deg);opacity:0}60%{-webkit-transform:skewX(20deg);transform:skewX(20deg)}60%,80%{opacity:1}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:none;transform:none;opacity:1}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}@keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);transform:translate3d(100%,0,0) skewX(30deg);opacity:0}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateIn{0%{transform-origin:center;-webkit-transform:rotate(-200deg);transform:rotate(-200deg);opacity:0}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:none;transform:none;opacity:1}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInDownRight{0%{transform-origin:right bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpLeft{0%{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}@keyframes rotateInUpRight{0%{transform-origin:right bottom;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);opacity:0}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:none;transform:none;opacity:1}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}@keyframes rotateOut{0%{transform-origin:center;opacity:1}0%,to{-webkit-transform-origin:center}to{transform-origin:center;-webkit-transform:rotate(200deg);transform:rotate(200deg);opacity:0}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}@keyframes rotateOutDownLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(45deg);transform:rotate(45deg);opacity:0}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutDownRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}@keyframes rotateOutUpLeft{0%{transform-origin:left bottom;opacity:1}0%,to{-webkit-transform-origin:left bottom}to{transform-origin:left bottom;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);opacity:0}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}@keyframes rotateOutUpRight{0%{transform-origin:right bottom;opacity:1}0%,to{-webkit-transform-origin:right bottom}to{transform-origin:right bottom;-webkit-transform:rotate(90deg);transform:rotate(90deg);opacity:0}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}@keyframes hinge{0%{transform-origin:top left}0%,20%,60%{-webkit-transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}20%,60%{-webkit-transform:rotate(80deg);transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-transform:rotate(60deg);transform:rotate(60deg);-webkit-transform-origin:top left;transform-origin:top left;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;opacity:1}to{-webkit-transform:translate3d(0,700px,0);transform:translate3d(0,700px,0);opacity:0}}.hinge{-webkit-animation-name:hinge;animation-name:hinge}@-webkit-keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes rollIn{0%{opacity:0;-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);transform:translate3d(-100%,0,0) rotate(-120deg)}to{opacity:1;-webkit-transform:none;transform:none}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}@keyframes rollOut{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(100%,0,0) rotate(120deg);transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInDown{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInLeft{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);transform:scale3d(.475,.475,.475) translate3d(10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInRight{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomInUp{0%{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}60%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}50%,to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutDown{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}@keyframes zoomOutLeft{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(-2000px,0,0);transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}@keyframes zoomOutRight{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{opacity:0;-webkit-transform:scale(.1) translate3d(2000px,0,0);transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}@keyframes zoomOutUp{40%{opacity:1;-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);transform:scale3d(.475,.475,.475) translate3d(0,60px,0);-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}to{opacity:0;-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;transform-origin:center bottom;-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);animation-timing-function:cubic-bezier(.175,.885,.32,1)}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp}
\ No newline at end of file
diff --git a/src/main/assets/font-awesome.min.css b/src/main/assets/font-awesome.min.css
deleted file mode 100644
index 9b27f8ea8..000000000
--- a/src/main/assets/font-awesome.min.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/*!
- * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome
- * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
- */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
diff --git a/src/main/assets/fonts/weather.ttf b/src/main/assets/fonts/weather.ttf
deleted file mode 100644
index 948f0a5d2..000000000
Binary files a/src/main/assets/fonts/weather.ttf and /dev/null differ
diff --git a/src/main/assets/images/layers-2x.png b/src/main/assets/images/layers-2x.png
deleted file mode 100644
index 200c333dc..000000000
Binary files a/src/main/assets/images/layers-2x.png and /dev/null differ
diff --git a/src/main/assets/images/layers.png b/src/main/assets/images/layers.png
deleted file mode 100644
index 1a72e5784..000000000
Binary files a/src/main/assets/images/layers.png and /dev/null differ
diff --git a/src/main/assets/images/marker-icon-2x.png b/src/main/assets/images/marker-icon-2x.png
deleted file mode 100644
index 88f9e5018..000000000
Binary files a/src/main/assets/images/marker-icon-2x.png and /dev/null differ
diff --git a/src/main/assets/images/marker-icon.png b/src/main/assets/images/marker-icon.png
deleted file mode 100644
index 950edf246..000000000
Binary files a/src/main/assets/images/marker-icon.png and /dev/null differ
diff --git a/src/main/assets/images/marker-shadow.png b/src/main/assets/images/marker-shadow.png
deleted file mode 100644
index 9fd297953..000000000
Binary files a/src/main/assets/images/marker-shadow.png and /dev/null differ
diff --git a/src/main/assets/jquery.min.js b/src/main/assets/jquery.min.js
deleted file mode 100644
index a1c07fd80..000000000
--- a/src/main/assets/jquery.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */
-!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"