diff options
Diffstat (limited to 'src/main/java')
30 files changed, 727 insertions, 141 deletions
diff --git a/src/main/java/de/tzur/conversations/Settings.java b/src/main/java/de/tzur/conversations/Settings.java new file mode 100644 index 00000000..c919d60c --- /dev/null +++ b/src/main/java/de/tzur/conversations/Settings.java @@ -0,0 +1,94 @@ +package de.tzur.conversations; + +import android.content.SharedPreferences; +import android.util.Log; + +/** + * This class is used to provide access to settings which have to be accessed frequently. + * Every setting in this class has to be updated using @see SettingsActivity#onSharedPreferenceChanged. + */ +public final class Settings { + + /** + * Initializes the settings provided via this static class. + * @param preferences the shared preferences of the app. + */ + public static void initSettingsClassWithPreferences(SharedPreferences preferences) { + Log.d("SETTING", "Initializing settings"); + String[] preferenceNames = { "parse_emoticons", "send_button_status", "led_notification_color", "auto_download_file_wlan", "auto_download_file_link", "confirm_messages_list" }; + for (String name : preferenceNames) { + Settings.synchronizeSettingsClassWithPreferences(preferences, name); + } + } + + /** + * Synchronizes the setting value in this class on settings update in SettingsActivity. + * @param preferences the shared preferences of the app. + * @param name the name of the setting to synchronize. + */ + public static void synchronizeSettingsClassWithPreferences(SharedPreferences preferences, String name) { + Log.d("SETTING", "Synchronizing settings"); + switch (name) { + case "parse_emoticons": + Settings.PARSE_EMOTICONS = preferences.getBoolean(name, Settings.PARSE_EMOTICONS); + break; + case "send_button_status": + Settings.SHOW_ONLINE_STATUS = preferences.getBoolean(name, Settings.SHOW_ONLINE_STATUS); + break; + case "led_notify_color": + Settings.LED_COLOR = preferences.getInt(name, Settings.LED_COLOR); + break; + case "auto_download_file_wlan": + Settings.DOWNLOAD_ONLY_WLAN = preferences.getBoolean(name, Settings.DOWNLOAD_ONLY_WLAN); + break; + case "auto_download_file_link": + Settings.DOWNLOAD_IMAGE_LINKS = preferences.getBoolean(name, Settings.DOWNLOAD_IMAGE_LINKS); + break; + case "confirm_messages_list": + int iPref = Settings.CONFIRM_MESSAGE_RECEIVED && Settings.CONFIRM_MESSAGE_READ ? 2 : Settings.CONFIRM_MESSAGE_RECEIVED ? 1 : 0; + try { + iPref = Integer.valueOf(preferences.getString(name, new Integer(iPref).toString())); + } catch (NumberFormatException e) { + // ignored, fallback-value set above + } + Settings.CONFIRM_MESSAGE_RECEIVED = iPref >= 1; + Settings.CONFIRM_MESSAGE_READ = iPref >= 2; + break; + } + } + /** + * Boolean if emoticons should be parsed to emoticons or not. + */ + public static boolean PARSE_EMOTICONS = true; + /** + * Boolean if online status should be shown or not. + */ + public static boolean SHOW_ONLINE_STATUS = true; + /** + * LED Color + */ + public static int LED_COLOR = 0xffffffff; + /** + * Boolean if image-links should be downloaded or not. + */ + public static boolean DOWNLOAD_IMAGE_LINKS = true; + /** + * Boolean if automatic downloads should be done only jif connected to WLAN. + */ + public static boolean DOWNLOAD_ONLY_WLAN = true; + /** + * Boolean if confirm received messages + */ + public static boolean CONFIRM_MESSAGE_RECEIVED = true; + /** + * Boolean if confirm read message + */ + public static boolean CONFIRM_MESSAGE_READ = true; + + /** + * This is a utility class - private constructor avoids any instantiation. + */ + private Settings() { + // Private constructor to avoid instantiation + } +} diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index f3cbffe6..f2303626 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -30,6 +30,8 @@ public final class Config { public static final int MESSAGE_MERGE_WINDOW = 20; + public static final boolean UTF8_EMOTICONS = false; + public static final int PAGE_SIZE = 50; public static final int MAX_NUM_PAGES = 3; diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index 8f8122f0..33089a8c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -17,6 +17,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; +import de.tzur.conversations.Settings; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -61,6 +62,8 @@ public class PgpEngine { final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager(); if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER + && Settings.DOWNLOAD_IMAGE_LINKS + && mXmppConnectionService.isDownloadAllowedInConnection() && manager.getAutoAcceptFileSize() > 0) { manager.createNewDownloadConnection(message); } @@ -167,7 +170,7 @@ public class PgpEngine { String[] lines = os.toString().split("\n"); for (int i = 2; i < lines.length - 1; ++i) { if (!lines[i].contains("Version")) { - encryptedMessageBody.append(lines[i].trim()); + encryptedMessageBody.append(lines[i]); } } message.setEncryptedBody(encryptedMessageBody @@ -242,7 +245,7 @@ public class PgpEngine { pgpSig.append("-----BEGIN PGP SIGNATURE-----"); pgpSig.append('\n'); pgpSig.append('\n'); - pgpSig.append(signature.replace("\n", "").trim()); + pgpSig.append(signature.replace("\n", "")); pgpSig.append('\n'); pgpSig.append("-----END PGP SIGNATURE-----"); Intent params = new Intent(); @@ -295,7 +298,7 @@ public class PgpEngine { sig = false; } else { if (!line.contains("Version")) { - signatureBuilder.append(line.trim()); + signatureBuilder.append(line); } } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 7a2dc3f7..a8b9252f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -130,6 +130,7 @@ public class Account extends AbstractEntity { private final Roster roster = new Roster(this); private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); + private XmppConnectionService mXmppConnectionService; public Account() { this.uuid = "0"; @@ -429,4 +430,8 @@ public class Account extends AbstractEntity { public boolean isOnlineAndConnected() { return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; } + + public XmppConnectionService getXmppConnectionService() { + return mXmppConnectionService; + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index cc6f146b..06c46c1c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -1,5 +1,7 @@ package eu.siacs.conversations.entities; +import android.graphics.Color; + import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -74,6 +76,11 @@ public class Bookmark extends Element implements ListItem { return tags; } + @Override + public int getStatusColor() { + return Color.parseColor("#259B23"); + } + public String getNick() { return this.findChildContent("nick"); } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index f924c05a..2d103faf 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; +import android.graphics.Color; import org.json.JSONArray; import org.json.JSONException; @@ -152,11 +153,16 @@ public class Contact implements ListItem, Blockable { return tags; } + @Override + public int getStatusColor() { + return Color.parseColor(UIHelper.getStatusColor(getMostAvailableStatus())); + } + public boolean match(String needle) { if (needle == null || needle.isEmpty()) { return true; } - needle = needle.toLowerCase(Locale.US).trim(); + needle = needle.toLowerCase(Locale.US); String[] parts = needle.split("\\s+"); if (parts.length > 1) { for(int i = 0; i < parts.length; ++i) { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 36d6b43b..1c985e2f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -775,10 +775,16 @@ public class Conversation extends AbstractEntity implements Blockable { synchronized (this.messages) { int count = 0; for(int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).isRead()) { + Message message = this.messages.get(i); + if (message.isRead()) { return count; } - ++count; + if (getMode() == Conversation.MODE_SINGLE + || account.getXmppConnectionService().getNotificationService().conferenceNotificationsEnabled() + || account.getXmppConnectionService().getNotificationService().wasHighlightedOrPrivate(message) + ) { + ++count; + } } return count; } diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java index efc1c2b9..aba2aef2 100644 --- a/src/main/java/eu/siacs/conversations/entities/ListItem.java +++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java @@ -11,6 +11,8 @@ public interface ListItem extends Comparable<ListItem> { public List<Tag> getTags(); + public int getStatusColor(); + public final class Tag { private final String name; private final int color; diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 0eff99cf..6a878e90 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -63,7 +63,7 @@ public class Message extends AbstractEntity { protected String conversationUuid; protected Jid counterpart; protected Jid trueCounterpart; - protected String body; + private String body; protected String encryptedBody; protected long timeSent; protected int encryption; @@ -345,16 +345,16 @@ public class Message extends AbstractEntity { public boolean equals(Message message) { if (this.serverMsgId != null && message.getServerMsgId() != null) { return this.serverMsgId.equals(message.getServerMsgId()); - } else if (this.body == null || this.counterpart == null) { + } else if (this.getBody() == null || this.counterpart == null) { return false; } else { String body, otherBody; if (this.hasFileOnRemoteHost()) { body = getFileParams().url.toString(); - otherBody = message.body == null ? null : message.body.trim(); + otherBody = message.getBody() == null ? null : message.getBody(); } else { - body = this.body; - otherBody = message.body; + body = this.getBody(); + otherBody = message.getBody(); } if (message.getRemoteMsgId() != null) { return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid)) @@ -411,13 +411,11 @@ public class Message extends AbstractEntity { this.getCounterpart().equals(message.getCounterpart()) && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && !GeoHelper.isGeoUri(message.getBody()) && - !GeoHelper.isGeoUri(this.body) && + !GeoHelper.isGeoUri(this.getBody()) && message.treatAsDownloadable() == Decision.NEVER && this.treatAsDownloadable() == Decision.NEVER && !message.getBody().startsWith(ME_COMMAND) && !this.getBody().startsWith(ME_COMMAND) && - !this.bodyIsHeart() && - !message.bodyIsHeart() && this.isTrusted() == message.isTrusted() ); } @@ -439,7 +437,7 @@ public class Message extends AbstractEntity { while(current.mergeable(current.next())) { current = current.next(); body.append(MERGE_SEPARATOR); - body.append(current.getBody().trim()); + body.append(current.getBody()); } return body.toString(); } @@ -506,17 +504,28 @@ public class Message extends AbstractEntity { private static String extractRelevantExtension(URL url) { String path = url.getPath(); + return extractRelevantExtension(path); + } + + private static String extractRelevantExtension(String path) { if (path == null || path.isEmpty()) { return null; } + String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); - String[] extensionParts = filename.split("\\."); - if (extensionParts.length == 2) { - return extensionParts[extensionParts.length - 1]; - } else if (extensionParts.length == 3 && Arrays - .asList(Transferable.VALID_CRYPTO_EXTENSIONS) - .contains(extensionParts[extensionParts.length - 1])) { - return extensionParts[extensionParts.length -2]; + + int dotPosition = filename.lastIndexOf("."); + + if (dotPosition != -1) + { + String extension = filename.substring(dotPosition + 1); + + // we want the real file extension, not the crypto one + if (Arrays.asList(Transferable.VALID_CRYPTO_EXTENSIONS).contains(extension)) { + return extractRelevantExtension(path.substring(0,dotPosition)); + } else { + return extension; + } } return null; } @@ -531,7 +540,7 @@ public class Message extends AbstractEntity { } } else { try { - return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim()))); + return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(this.getBody()))); } catch (MalformedURLException e) { return null; } @@ -539,7 +548,12 @@ public class Message extends AbstractEntity { } public Decision treatAsDownloadable() { - if (body.trim().contains(" ")) { + /** + * there are a few cases where spaces result in an unwanted behavior, e.g. + * "http://example.com/image.jpg" text that will not be shown /abc.png" + * or more than one image link in one message. + */ + if (getBody().contains(" ")) { return Decision.NEVER; } try { @@ -572,10 +586,6 @@ public class Message extends AbstractEntity { } } - public boolean bodyIsHeart() { - return body != null && UIHelper.HEARTS.contains(body.trim()); - } - public FileParams getFileParams() { FileParams params = getLegacyFileParams(); if (params != null) { @@ -585,10 +595,10 @@ public class Message extends AbstractEntity { if (this.transferable != null) { params.size = this.transferable.getFileSize(); } - if (body == null) { + if (this.getBody() == null) { return params; } - String parts[] = body.split("\\|"); + String parts[] = this.getBody().split("\\|"); switch (parts.length) { case 1: try { @@ -647,10 +657,10 @@ public class Message extends AbstractEntity { public FileParams getLegacyFileParams() { FileParams params = new FileParams(); - if (body == null) { + if (this.getBody() == null) { return params; } - String parts[] = body.split(","); + String parts[] = this.getBody().split(","); if (parts.length == 3) { try { params.size = Long.parseLong(parts[0]); diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 69bb1803..3a256219 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Locale; import java.util.TimeZone; +import de.tzur.conversations.Settings; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.PhoneHelper; @@ -72,7 +73,7 @@ public abstract class AbstractGenerator { s.append(feature + "<"); } byte[] sha1 = md.digest(s.toString().getBytes()); - return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); + return new String(Base64.encode(sha1, Base64.DEFAULT)); } public static String getTimestamp(long time) { @@ -83,7 +84,7 @@ public abstract class AbstractGenerator { public List<String> getFeatures() { ArrayList<String> features = new ArrayList<>(); features.addAll(Arrays.asList(FEATURES)); - if (mXmppConnectionService.confirmMessages()) { + if (Settings.CONFIRM_MESSAGE_RECEIVED) { features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES)); } Collections.sort(features); diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 0d202bb9..356f9417 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -165,7 +165,7 @@ public class HttpDownloadConnection implements Transferable { return; } file.setExpectedSize(size); - if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) { + if (size != -1 && size <= mHttpConnectionManager.getAutoAcceptFileSize()) { HttpDownloadConnection.this.acceptedAutomatically = true; new Thread(new FileDownloader(interactive)).start(); } else { @@ -176,25 +176,22 @@ public class HttpDownloadConnection implements Transferable { } private long retrieveFileSize() throws IOException { + Log.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive)); + changeStatus(STATUS_CHECKING); + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); + connection.setRequestMethod("HEAD"); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + if (contentLength == null) { + return -1; + } try { - Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive)); - changeStatus(STATUS_CHECKING); - HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); - connection.setRequestMethod("HEAD"); - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - String contentLength = connection.getHeaderField("Content-Length"); - connection.disconnect(); - if (contentLength == null) { - throw new IOException(); - } return Long.parseLong(contentLength, 10); - } catch (IOException e) { - throw e; } catch (NumberFormatException e) { - throw new IOException(); + return -1; } } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index 18331796..3f11f756 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -1,16 +1,21 @@ package eu.siacs.conversations.parser; +import android.util.Log; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; + import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.MessagePacket; public abstract class AbstractParser { @@ -20,6 +25,15 @@ public abstract class AbstractParser { this.mXmppConnectionService = service; } + /** + * Gets the timestamp from the 'delay' element. + * Refer to XEP-0203: Delayed Delivery for details. @link{http://xmpp.org/extensions/xep-0203.html} + * @param packet the element to find the child element 'delay' in. + * @return the time in milli seconds of the attribute 'stamp' of the + * element 'delay'. In case there is no 'delay' element or no 'stamp' + * attribute or the current time is less than the value of the 'stamp' + * attribute the current time is returned. + */ public static Long getTimestamp(Element element, Long defaultValue) { Element delay = element.findChild("delay","urn:xmpp:delay"); if (delay != null) { @@ -39,12 +53,27 @@ public abstract class AbstractParser { return getTimestamp(packet,System.currentTimeMillis()); } + /** + * Parses the timestamp according to XEP-0082: XMPP Date and Time Profiles. + * @link{http://xmpp.org/extensions/xep-0082.html} + * + * @param timestamp the timestamp to parse + * @return Date + * @throws ParseException + */ public static Date parseTimestamp(String timestamp) throws ParseException { - timestamp = timestamp.replace("Z", "+0000"); - SimpleDateFormat dateFormat; - timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length()); - dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); - return dateFormat.parse(timestamp); + /*try { + Log.d("TIMESTAMP", timestamp); + return DatatypeFactory.newInstance().newXMLGregorianCalendar(timestamp).toGregorianCalendar().getTime(); + } catch (DatatypeConfigurationException e) { + Log.d("TIMESTAMP", e.getMessage()); + return new Date(); + }*/ + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length()); + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); + return dateFormat.parse(timestamp); } protected void updateLastseen(final Element packet, final Account account, final boolean presenceOverwrite) { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 025ed1e7..e2f13bd3 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -8,6 +8,7 @@ import net.java.otr4j.session.SessionStatus; import java.util.Set; +import de.tzur.conversations.Settings; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; @@ -117,6 +118,8 @@ public class MessageParser extends AbstractParser implements this.jid = jid; this.password = password; } + return false; + } public boolean execute(Account account) { if (jid != null) { diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 90e4d216..d03dcb84 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import de.tzur.conversations.Settings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; @@ -218,7 +219,7 @@ public class NotificationService { mBuilder.setDefaults(0); mBuilder.setSmallIcon(R.drawable.ic_notification); mBuilder.setDeleteIntent(createDeleteIntent()); - mBuilder.setLights(0xff00FF00, 2000, 3000); + mBuilder.setLights(Settings.LED_COLOR, 2000, 4000); final Notification notification = mBuilder.build(); notificationManager.notify(NOTIFICATION_ID, notification); } @@ -442,7 +443,7 @@ public class NotificationService { return PendingIntent.getService(mXmppConnectionService,0,intent,PendingIntent.FLAG_UPDATE_CURRENT); } - private boolean wasHighlightedOrPrivate(final Message message) { + public boolean wasHighlightedOrPrivate(final Message message) { final String nick = message.getConversation().getMucOptions().getActualNick(); final Pattern highlight = generateNickHighlightPattern(nick); if (message.getBody() == null || nick == null) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 36d6cd49..b165ede4 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -49,6 +49,7 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import de.duenndns.ssl.MemorizingTrustManager; +import de.tzur.conversations.Settings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; @@ -579,6 +580,25 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return activeNetwork != null && activeNetwork.isConnected(); } + /** + * check whether we are allowed to download at the moment + */ + public boolean isDownloadAllowedInConnection() { + if (Settings.DOWNLOAD_ONLY_WLAN) { + return isWifiConnected(); + } + return true; + } + + /** + * check whether wifi is connected + */ + public boolean isWifiConnected() { + ConnectivityManager cm = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo niWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + return niWifi.isConnected(); + } + @SuppressLint("TrulyRandom") @Override public void onCreate() { @@ -1069,6 +1089,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { Log.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) { + Log.d("mam", "Query in progress"); return; } Runnable runnable = new Runnable() { @@ -1076,19 +1097,26 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void run() { final Account account = conversation.getAccount(); List<Message> messages = databaseBackend.getMessages(conversation, 50,timestamp); + Log.d("mam", "runnable load more messages"); if (messages.size() > 0) { + Log.d("mam", "At least one message"); conversation.addAll(0, messages); checkDeletedFiles(conversation); callback.onMoreMessagesLoaded(messages.size(), conversation); } else if (conversation.hasMessagesLeftOnServer() && account.isOnlineAndConnected() && account.getXmppConnection().getFeatures().mam()) { + Log.d("mam", "mam activate, account online and connected and messages left on server"); MessageArchiveService.Query query = getMessageArchiveService().query(conversation,0,timestamp - 1); if (query != null) { query.setCallback(callback); } callback.informUser(R.string.fetching_history_from_server); - } + } else { + Log.d("mam", ((!conversation.hasMessagesLeftOnServer()) ? "no" : "") + " more messages left on server, mam " + ((account.getXmppConnection().getFeatures().mam()) ? "" : "not") + " activated, account is " + ((account.isOnlineAndConnected()) ? "" : "not") + " online or connected)"); + callback.onMoreMessagesLoaded(0, conversation); + callback.informUser(R.string.no_more_history_on_server); + } } }; mDatabaseExecutor.execute(runnable); @@ -2371,7 +2399,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void sendReadMarker(final Conversation conversation) { final Message markable = conversation.getLatestMarkableMessage(); this.markRead(conversation); - if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) { + if (Settings.CONFIRM_MESSAGE_READ && markable != null && markable.getRemoteMsgId() != null) { Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString()); Account account = conversation.getAccount(); final Jid to = markable.getCounterpart(); @@ -2549,7 +2577,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void clearConversationHistory(final Conversation conversation) { conversation.clearMessages(); - conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam + /* + * In case the history was loaded completely before. + * The flag "hasMessagesLeftOnServer" is set to false and no messages will be loaded anymore + * Therefore set this flag to true and try to get messages from server + */ + conversation.setHasMessagesLeftOnServer(true); new Thread(new Runnable() { @Override public void run() { diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java index aa986bd1..09988b7c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java @@ -26,11 +26,14 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti mCurrentPassword.requestFocus(); mCurrentPassword.setError(getString(R.string.account_status_unauthorized)); } else if (!newPassword.equals(newPasswordConfirm)) { - mNewPasswordConfirm.requestFocus(); - mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); + mNewPasswordConfirm.requestFocus(); + mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); + } else if (newPassword.isEmpty()) { + mNewPassword.requestFocus(); + mNewPassword.setError(getString(R.string.password_should_not_be_empty)); } else if (newPassword.trim().isEmpty()) { mNewPassword.requestFocus(); - mNewPassword.setError(getString(R.string.password_should_not_be_empty)); + mNewPassword.setError(getString(R.string.password_should_not_contain_only_spaces)); } else { mCurrentPassword.setError(null); mNewPassword.setError(null); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 219a4fca..7fec827b 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -53,6 +53,7 @@ import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; +import github.ankushsachdeva.emojicon.EmojiconEditText; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast { @@ -257,6 +258,7 @@ public class ConversationActivity extends XmppActivity } }); listView.enableSwipeToDismiss(); + listView.setSwipeDirection(EnhancedListView.SwipeDirection.START); listView.setSwipingLayout(R.id.swipeable_item); listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP); listView.setUndoHideDelay(5000); @@ -1144,7 +1146,7 @@ public class ConversationActivity extends XmppActivity } prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); prepareFileToast.show(); - xmppConnectionService.attachFileToConversation(conversation,uri, new UiCallback<Message>() { + xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback<Message>() { @Override public void success(Message message) { hidePrepareFileToast(); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index de758efc..f645b241 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -11,7 +11,9 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; import android.text.InputType; +import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -23,12 +25,12 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.ListView; +import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -47,7 +49,13 @@ import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +<<<<<<< HEAD +import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.DownloadableFile; +import eu.siacs.conversations.entities.TransferablePlaceholder; +======= import eu.siacs.conversations.entities.DownloadableFile; +>>>>>>> 1.6.3 import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.Presences; @@ -59,10 +67,14 @@ import eu.siacs.conversations.ui.XmppActivity.OnValueEdited; import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; +import eu.siacs.conversations.ui.listeners.ConversationSwipeRefreshListener; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jid.Jid; +import github.ankushsachdeva.emojicon.EmojiconGridView; +import github.ankushsachdeva.emojicon.EmojiconsPopup; +import github.ankushsachdeva.emojicon.emoji.Emojicon; public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener { @@ -101,10 +113,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } }; protected ListView messagesView; - final protected List<Message> messageList = new ArrayList<>(); + protected SwipeRefreshLayout swipeLayout; + final protected List<Message> messageList = new ArrayList<>(); protected MessageAdapter messageListAdapter; private EditMessage mEditMessage; private ImageButton mSendButton; + private ImageView mEmojButton; + private View mRootView; + private EmojiconsPopup mEmojPopup; private RelativeLayout snackbar; private TextView snackbarMessage; private TextView snackbarAction; @@ -371,6 +387,107 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa }); mEditMessage.setOnEditorActionListener(mEditorActionListener); + // Start of emojicon + mEmojButton = (ImageView) view.findViewById(R.id.emoji_btn); + mRootView = view.findViewById(R.id.textsend); + + // Give the topmost view of your activity layout hierarchy. This will be used to measure soft keyboard height + mEmojPopup = new EmojiconsPopup(mRootView, this.getActivity()); + + //Will automatically set size according to the soft keyboard size + mEmojPopup.setSizeForSoftKeyboard(); + + //If the emoji popup is dismissed, change emojiButton to smiley icon + mEmojPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { + + @Override + public void onDismiss() { + changeEmojiKeyboardIcon(mEmojButton, R.drawable.smiley); + } + }); + + //If the text keyboard closes, also dismiss the emoji popup + mEmojPopup.setOnSoftKeyboardOpenCloseListener(new EmojiconsPopup.OnSoftKeyboardOpenCloseListener() { + + @Override + public void onKeyboardOpen(int keyBoardHeight) { + + } + + @Override + public void onKeyboardClose() { + if (mEmojPopup.isShowing()) + mEmojPopup.dismiss(); + } + }); + + //On emoji clicked, add it to edittext + mEmojPopup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() { + + @Override + public void onEmojiconClicked(Emojicon emojicon) { + if (mEditMessage == null || emojicon == null) { + return; + } + + int start = mEditMessage.getSelectionStart(); + int end = mEditMessage.getSelectionEnd(); + if (start < 0) { + mEditMessage.append(emojicon.getEmoji()); + } else { + mEditMessage.getText().replace(Math.min(start, end), + Math.max(start, end), emojicon.getEmoji(), 0, + emojicon.getEmoji().length()); + } + } + }); + + //On backspace clicked, emulate the KEYCODE_DEL key event + mEmojPopup.setOnEmojiconBackspaceClickedListener(new EmojiconsPopup.OnEmojiconBackspaceClickedListener() { + + @Override + public void onEmojiconBackspaceClicked(View v) { + KeyEvent event = new KeyEvent( + 0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL); + mEditMessage.dispatchKeyEvent(event); + } + }); + + // To toggle between text keyboard and emoji keyboard keyboard(Popup) + mEmojButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + + //If popup is not showing => emoji keyboard is not visible, we need to show it + if(!mEmojPopup.isShowing()){ + + //If keyboard is visible, simply show the emoji popup + if(mEmojPopup.isKeyBoardOpen()){ + mEmojPopup.showAtBottom(); + changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard); + } + + //else, open the text keyboard first and immediately after that show the emoji popup + else{ + mEditMessage.setFocusableInTouchMode(true); + mEditMessage.requestFocus(); + mEmojPopup.showAtBottomPending(); + final InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT); + changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard); + } + } + + //If popup is showing, simply dismiss it to show the undelying text keyboard + else{ + mEmojPopup.dismiss(); + } + } + }); + + // End of emojicon + mSendButton = (ImageButton) view.findViewById(R.id.textSendButton); mSendButton.setOnClickListener(this.mSendButtonListener); @@ -434,6 +551,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa registerForContextMenu(messagesView); + // Start of swipe refresh + // New Swipe refresh + swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_container); + swipeLayout.setOnRefreshListener(new ConversationSwipeRefreshListener(messageList, swipeLayout, this, messagesView, messageListAdapter)); + // End of swipe refresh + return view; } @@ -572,7 +695,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa resId = R.string.file_url; url = message.getFileParams().url.toString(); } else { - url = message.getBody().trim(); + url = message.getBody(); resId = R.string.file_url; } if (activity.copyTextToClipboard(url, resId)) { @@ -603,7 +726,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } protected void highlightInConference(String nick) { - String oldString = mEditMessage.getText().toString().trim(); + String oldString = mEditMessage.getText().toString(); if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { mEditMessage.getText().insert(0, nick + ": "); } else { @@ -669,6 +792,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (size > 0) { messagesView.setSelection(size - 1); } + swipeLayout.setRefreshing(false); } private OnClickListener mUnblockClickListener = new OnClickListener() { @@ -1151,15 +1275,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; activity.selectPresence(message.getConversation(), - new OnPresenceSelected() { - - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - xmppService.sendMessage(message); - messageSent(); - } - }); + new OnPresenceSelected() { + + @Override + public void onPresenceSelected() { + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + }); } public void appendText(String text) { @@ -1224,4 +1348,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } + private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){ + iconToBeChanged.setImageResource(drawableResourceId); + } + } diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index a58cf2b8..a7aa2024 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -7,8 +7,9 @@ import android.view.KeyEvent; import android.widget.EditText; import eu.siacs.conversations.Config; +import github.ankushsachdeva.emojicon.EmojiconEditText; -public class EditMessage extends EditText { +public class EditMessage extends EmojiconEditText { public EditMessage(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 6e5fe610..f9238f13 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -1,6 +1,21 @@ package eu.siacs.conversations.ui; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import de.tzur.conversations.Settings; +import de.duenndns.ssl.MemorizingTrustManager; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xmpp.XmppConnection; +import github.ankushsachdeva.emojicon.EmojiconHandler; + import android.app.AlertDialog; +import android.app.Fragment; import android.app.FragmentManager; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -12,17 +27,6 @@ import android.preference.Preference; import android.preference.PreferenceManager; import android.widget.Toast; -import java.security.KeyStoreException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; - -import de.duenndns.ssl.MemorizingTrustManager; -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.xmpp.XmppConnection; - public class SettingsActivity extends XmppActivity implements OnSharedPreferenceChangeListener { private SettingsFragment mSettingsFragment; @@ -130,37 +134,46 @@ public class SettingsActivity extends XmppActivity implements @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String name) { - if (name.equals("resource")) { - String resource = preferences.getString("resource", "mobile") - .toLowerCase(Locale.US); - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - if (account.setResource(resource)) { + // need to synchronize the settings class first + Settings.synchronizeSettingsClassWithPreferences(getPreferences(), name); + switch (name) { + case "resource": + String resource = preferences.getString("resource", "mobile") + .toLowerCase(Locale.US); + if (xmppConnectionServiceBound) { + for (Account account : xmppConnectionService.getAccounts()) { + if (account.setResource(resource)) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.resetStreamId(); + } + xmppConnectionService.reconnectAccountInBackground(account); + } + } + } + } + break; + case "keep_foreground_service": + xmppConnectionService.toggleForegroundService(); + break; + case "confirm_messages": + if (xmppConnectionServiceBound) { + for (Account account : xmppConnectionService.getAccounts()) { if (!account.isOptionSet(Account.OPTION_DISABLED)) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.resetStreamId(); - } - xmppConnectionService.reconnectAccountInBackground(account); + xmppConnectionService.sendPresence(account); } } } - } - } else if (name.equals("keep_foreground_service")) { - xmppConnectionService.toggleForegroundService(); - } else if (name.equals("confirm_messages")) { - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - xmppConnectionService.sendPresence(account); - } - } - } - } else if (name.equals("dont_trust_system_cas")) { - xmppConnectionService.updateMemorizingTrustmanager(); - reconnectAccounts(); + break; + case "dont_trust_system_cas": + xmppConnectionService.updateMemorizingTrustmanager(); + reconnectAccounts(); + break; + case "parse_emoticons": + EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS); + break; } - } private void displayToast(final String msg) { diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 7734dc11..2d780eda 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -68,6 +68,7 @@ import java.util.Hashtable; import java.util.List; import java.util.concurrent.RejectedExecutionException; +import de.tzur.conversations.Settings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; @@ -361,6 +362,9 @@ public abstract class XmppActivity extends Activity { setTheme(this.mTheme); this.mUsingEnterKey = usingEnterKey(); mUseSubject = getPreferences().getBoolean("use_subject", true); + + Settings.initSettingsClassWithPreferences(getPreferences()); + final ActionBar ab = getActionBar(); if (ab!=null) { ab.setDisplayHomeAsUpEnabled(true); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java index 98250af9..141dbc3a 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java @@ -8,6 +8,7 @@ import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Switch; import java.util.List; diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index 6918713e..b89dd01a 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -19,13 +19,29 @@ import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.RejectedExecutionException; +import de.tzur.conversations.Settings; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.utils.UIHelper; +import github.ankushsachdeva.emojicon.EmojiconTextView; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; public class ConversationAdapter extends ArrayAdapter<Conversation> { @@ -56,10 +72,34 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { } else { convName.setText(conversation.getJid().toBareJid().toString()); } - TextView mLastMessage = (TextView) view.findViewById(R.id.conversation_lastmsg); + EmojiconTextView mLastMessage = (EmojiconTextView) view.findViewById(R.id.conversation_lastmsg); TextView mTimestamp = (TextView) view.findViewById(R.id.conversation_lastupdate); ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage); + if (Settings.SHOW_ONLINE_STATUS && conversation != null && conversation.getAccount().getStatus() == Account.State.ONLINE) { + TextView status = (TextView) view.findViewById(R.id.status); + + String color = "#000000"; + if (conversation.getMode() == Conversation.MODE_SINGLE) { + switch (conversation.getContact().getMostAvailableStatus()) { + case Presences.ONLINE: + case Presences.CHAT: + color = "#259B23"; + break; + case Presences.AWAY: + case Presences.XA: + color = "#FF9800"; + break; + case Presences.DND: + color = "#E51C23"; + break; + } + } else if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().online()) { + color = "#259B23"; + } + status.setBackgroundColor(Color.parseColor(color)); + } + Message message = conversation.getLatestMessage(); if (!conversation.isRead()) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index ad7d7622..4187ec60 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -1,5 +1,16 @@ package eu.siacs.conversations.ui.adapter; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + +import de.tzur.conversations.Settings; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.jid.Jid; + import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; @@ -16,16 +27,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.concurrent.RejectedExecutionException; - -import eu.siacs.conversations.R; -import eu.siacs.conversations.entities.ListItem; -import eu.siacs.conversations.ui.XmppActivity; -import eu.siacs.conversations.utils.UIHelper; -import eu.siacs.conversations.xmpp.jid.Jid; - public class ListItemAdapter extends ArrayAdapter<ListItem> { protected XmppActivity activity; @@ -57,6 +58,12 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { if (view == null) { view = inflater.inflate(R.layout.contact, parent, false); } + + if (Settings.SHOW_ONLINE_STATUS) { + TextView tvStatus = (TextView) view.findViewById(R.id.contact_status); + tvStatus.setBackgroundColor(item.getStatusColor()); + } + TextView tvName = (TextView) view.findViewById(R.id.contact_display_name); TextView tvJid = (TextView) view.findViewById(R.id.contact_jid); ImageView picture = (ImageView) view.findViewById(R.id.contact_photo); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 1ddd6c44..a5b25967 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -6,6 +6,7 @@ import android.content.pm.ResolveInfo; import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; +import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; @@ -26,6 +27,8 @@ import android.widget.Toast; import java.util.List; +import de.tzur.conversations.Settings; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; @@ -38,6 +41,7 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; +import github.ankushsachdeva.emojicon.EmojiconTextView; public class MessageAdapter extends ArrayAdapter<Message> { @@ -279,13 +283,14 @@ public class MessageAdapter extends ArrayAdapter<Message> { i = body.indexOf(Message.MERGE_SEPARATOR,end); } if (message.getType() != Message.TYPE_PRIVATE) { + if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { - viewHolder.messageBody.setText(formattedBody); + viewHolder.messageBody.setText(formattedBody); } } else { String privateMarker; @@ -424,7 +429,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (EmojiconTextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -444,7 +449,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (EmojiconTextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -555,8 +560,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); - } else if (message.bodyIsHeart()) { - displayHeartMessage(viewHolder, message.getBody().trim()); } else if (message.treatAsDownloadable() == Message.Decision.MUST) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { @@ -632,7 +635,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected ImageView indicator; protected ImageView indicatorReceived; protected TextView time; - protected TextView messageBody; + protected EmojiconTextView messageBody; protected ImageView contact_picture; protected TextView status_message; } diff --git a/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java new file mode 100644 index 00000000..2154f8d8 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java @@ -0,0 +1,118 @@ +package eu.siacs.conversations.ui.listeners; + +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.View; +import android.widget.ListView; +import android.widget.Toast; + +import java.util.List; + +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.ui.ConversationFragment; +import eu.siacs.conversations.ui.adapter.MessageAdapter; + +/** + * This listener updates the UI when messages are loaded from the server. + */ +public class ConversationMoreMessagesLoadedListener implements XmppConnectionService.OnMoreMessagesLoaded { + private SwipeRefreshLayout swipeLayout; + private List<Message> messageList; + private ConversationFragment fragment; + private ListView messagesView; + private MessageAdapter messageListAdapter; + private Toast messageLoaderToast; + + public ConversationMoreMessagesLoadedListener(SwipeRefreshLayout swipeLayout, List<Message> messageList, ConversationFragment fragment, ListView messagesView, MessageAdapter messageListAdapter) { + this.swipeLayout = swipeLayout; + this.messageList = messageList; + this.fragment = fragment; + this.messagesView = messagesView; + this.messageListAdapter = messageListAdapter; + } + + @Override + public void onMoreMessagesLoaded(final int c, final Conversation conversation) { + ConversationActivity activity = (ConversationActivity) fragment.getActivity(); + if (activity.getSelectedConversation() != conversation) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + swipeLayout.setRefreshing(false); + } + }); + return; + } + if (0 == c) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + swipeLayout.setRefreshing(false); + } + }); + } + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final int oldPosition = messagesView.getFirstVisiblePosition(); + int pos = 0; + View v = messagesView.getChildAt(0); + final int pxOffset = (v == null) ? 0 : v.getTop(); + if (-1 < oldPosition && messageList.size() > oldPosition) { + Message message = messageList.get(oldPosition); + String uuid = message != null ? message.getUuid() : null; + pos = getIndexOf(uuid, messageList); + } + conversation.populateWithMessages(messageList); + fragment.updateStatusMessages(); + messageListAdapter.notifyDataSetChanged(); + messagesView.setSelectionFromTop(pos, pxOffset); + + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + swipeLayout.setRefreshing(false); + } + }); + } + + @Override + public void informUser(final int resId) { + final ConversationActivity activity = (ConversationActivity) fragment.getActivity(); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (messageLoaderToast != null) { + messageLoaderToast.cancel(); + } + messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG); + messageLoaderToast.show(); + } + }); + + } + + private int getIndexOf(String uuid, List<Message> messages) { + if (uuid == null) { + return 0; + } + for(int i = 0; i < messages.size(); ++i) { + if (uuid.equals(messages.get(i).getUuid())) { + return i; + } else { + Message next = messages.get(i); + while(next != null && next.wasMergedIntoPrevious()) { + if (uuid.equals(next.getUuid())) { + return i; + } + next = next.next(); + } + + } + } + return 0; + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java new file mode 100644 index 00000000..09a8d730 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java @@ -0,0 +1,48 @@ +package eu.siacs.conversations.ui.listeners; + +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.View; +import android.widget.ListView; +import android.widget.Toast; + +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.ui.ConversationFragment; +import eu.siacs.conversations.ui.adapter.MessageAdapter; + +/** + * This listener starts loading messages from the server. + */ +public class ConversationSwipeRefreshListener implements SwipeRefreshLayout.OnRefreshListener { + private List<Message> messageList; + private ConversationFragment fragment; + private ConversationMoreMessagesLoadedListener listener; + + public ConversationSwipeRefreshListener(List<Message> messageList, SwipeRefreshLayout swipeLayout, ConversationFragment fragment, ListView messagesView, MessageAdapter messageListAdapter) { + this.messageList = messageList; + this.fragment = fragment; + this.listener = new ConversationMoreMessagesLoadedListener(swipeLayout, messageList, fragment, messagesView, messageListAdapter); + } + + @Override + public void onRefresh() { + Log.d(Config.LOGTAG, "Refresh swipe container"); + synchronized (this.messageList) { + long timestamp; + if (messageList.isEmpty()) { + timestamp = System.currentTimeMillis(); + } else { + timestamp = this.messageList.get(0).getTimeSent(); + } + ConversationActivity activity = (ConversationActivity) fragment.getActivity(); + activity.xmppConnectionService.loadMoreMessages(activity.getSelectedConversation(), timestamp, this.listener); + } + Log.d(Config.LOGTAG, "End Refresh swipe container"); + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index cac23f07..44a38728 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -11,11 +11,13 @@ import java.util.Calendar; import java.util.Date; import java.util.Locale; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.xmpp.jid.Jid; public class UIHelper { @@ -190,7 +192,7 @@ public class UIHelper { return new Pair<>(context.getString(R.string.location), true); } } else{ - return new Pair<>(message.getBody().trim(), false); + return new Pair<>(message.getBody(), false); } } } @@ -241,13 +243,27 @@ public class UIHelper { } } + public static String getStatusColor(int status) { + switch (status) { + case Presences.ONLINE: + case Presences.CHAT: + return "#259B23"; + case Presences.AWAY: + case Presences.XA: + return "#FF9800"; + case Presences.DND: + return "#E51C23"; + } + return "#CCCCCC"; + } + private static String getDisplayedMucCounterpart(final Jid counterpart) { if (counterpart==null) { return ""; } else if (!counterpart.isBareJid()) { - return counterpart.getResourcepart().trim(); + return counterpart.getResourcepart(); } else { - return counterpart.toString().trim(); + return counterpart.toString(); } } @@ -257,7 +273,7 @@ public class UIHelper { || message.getType() != Message.TYPE_TEXT) { return false; } - String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault()); + String body = message.getBody() == null ? null : message.getBody().toLowerCase(Locale.getDefault()); body = body.replace("?","").replace("¿",""); return LOCATION_QUESTIONS.contains(body); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index 4f733b10..42b14330 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -355,8 +355,10 @@ public class JingleConnection implements Transferable { message.setBody(Long.toString(size)); conversation.add(message); mXmppConnectionService.updateConversationUi(); - if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) { - Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom()); + if (size <= this.mJingleConnectionManager.getAutoAcceptFileSize() + && mXmppConnectionService.isDownloadAllowedInConnection()) { + Log.d(Config.LOGTAG, "auto accepting file from " + + packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index ab564480..2c888d0f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -11,6 +11,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.AbstractConnectionManager; |