textViews = new ArrayList<>();
+ final LinearLayout container = convertView.findViewById(R.id.messages_container);
+
+ // remove older blocks
+ container.removeAllViews();
+
+ final String[] blocks = mHelper.getFencedCodeBlocks(message);
+ final String START_FB = VectorMessagesAdapterHelper.START_FENCED_BLOCK;
+ final String END_FB = VectorMessagesAdapterHelper.END_FENCED_BLOCK;
+ for (final String block : blocks) {
+ if (block.startsWith(START_FB) && block.endsWith(END_FB)) {
+ // Fenced block
+ final String minusTags = block
+ .substring(START_FB.length(), block.length() - END_FB.length())
+ .replace("\n", "
")
+ .replace(" ", " ");
+ final View blockView = mLayoutInflater.inflate(R.layout.adapter_item_vector_message_code_block, null);
+ container.addView(blockView);
+ final TextView tv = blockView.findViewById(R.id.messagesAdapter_body);
+ highlightPattern(tv, new SpannableString(minusTags),
+ TextUtils.equals(Message.FORMAT_MATRIX_HTML, message.format) ? mHelper.getSanitisedHtml(minusTags) : null,
+ mPattern, shouldHighlighted);
+
+ mHelper.highlightFencedCode(tv);
+ textViews.add(tv);
+
+ ((View) tv.getParent()).setBackgroundColor(ThemeUtils.getColor(mContext, R.attr.markdown_block_background_color));
+ } else {
+ // Not a fenced block
+ final TextView tv = (TextView) mLayoutInflater.inflate(R.layout.adapter_item_vector_message_code_text, null);
+
+ highlightPattern(tv, new SpannableString(block),
+ TextUtils.equals(Message.FORMAT_MATRIX_HTML, message.format) ? mHelper.getSanitisedHtml(block) : null,
+ mPattern, shouldHighlighted);
+
+ container.addView(tv);
+ textViews.add(tv);
+ }
+ }
+
+ return textViews;
+ }
+
/**
* Image / Video message management
*
@@ -1226,7 +1309,7 @@ private View getImageVideoView(int type, final int position, View convertView, V
this.manageSubView(position, convertView, imageLayout, type);
ImageView imageView = convertView.findViewById(R.id.messagesAdapter_image);
- addContentViewListeners(convertView, imageView, position);
+ addContentViewListeners(convertView, imageView, position, type);
} catch (Exception e) {
Log.e(LOG_TAG, "## getImageVideoView() failed : " + e.getMessage());
}
@@ -1275,7 +1358,7 @@ private View getNoticeRoomMemberView(final int viewType, final int position, Vie
View textLayout = convertView.findViewById(R.id.messagesAdapter_text_layout);
this.manageSubView(position, convertView, textLayout, viewType);
- addContentViewListeners(convertView, noticeTextView, position);
+ addContentViewListeners(convertView, noticeTextView, position, viewType);
// android seems having a big issue when the text is too long and an alpha !=1 is applied:
// ---> the text is not displayed.
@@ -1286,6 +1369,9 @@ private View getNoticeRoomMemberView(final int viewType, final int position, Vie
// the patch apply the alpha to the text color but it does not work for the hyperlinks.
noticeTextView.setAlpha(1.0f);
noticeTextView.setTextColor(getNoticeTextColor());
+
+ Message message = JsonUtils.toMessage(msg.getContent());
+ mHelper.manageURLPreviews(message, convertView, msg.eventId);
} catch (Exception e) {
Log.e(LOG_TAG, "## getNoticeRoomMemberView() failed : " + e.getMessage());
}
@@ -1352,7 +1438,9 @@ private View getEmoteView(final int position, View convertView, ViewGroup parent
View textLayout = convertView.findViewById(R.id.messagesAdapter_text_layout);
this.manageSubView(position, convertView, textLayout, ROW_TYPE_EMOTE);
- addContentViewListeners(convertView, emoteTextView, position);
+ addContentViewListeners(convertView, emoteTextView, position, ROW_TYPE_EMOTE);
+
+ mHelper.manageURLPreviews(message, convertView, event.eventId);
} catch (Exception e) {
Log.e(LOG_TAG, "## getEmoteView() failed : " + e.getMessage());
}
@@ -1403,7 +1491,7 @@ private View getFileView(final int position, View convertView, ViewGroup parent)
View fileLayout = convertView.findViewById(R.id.messagesAdapter_file_layout);
this.manageSubView(position, convertView, fileLayout, ROW_TYPE_FILE);
- addContentViewListeners(convertView, fileTextView, position);
+ addContentViewListeners(convertView, fileTextView, position, ROW_TYPE_FILE);
} catch (Exception e) {
Log.e(LOG_TAG, "## getFileView() failed " + e.getMessage());
}
@@ -1559,51 +1647,56 @@ private void highlightPattern(TextView textView, Spannable text, String htmlForm
* @return true if should be added
*/
private boolean isSupportedRow(MessageRow row) {
- boolean isSupported = VectorMessagesAdapterHelper.isDisplayableEvent(mContext, row);
-
- if (isSupported) {
- String eventId = row.getEvent().eventId;
-
- MessageRow currentRow = mEventRowMap.get(eventId);
+ Event event = row.getEvent();
- // the row should be added only if the message has not been received
- isSupported = (null == currentRow);
+ // sanity checks
+ if ((null == event) || (null == event.eventId)) {
+ Log.e(LOG_TAG, "## isSupportedRow() : invalid row");
+ return false;
+ }
- // check if the message is already received
- if (null != currentRow) {
- // waiting for echo
- // the message is displayed as sent event if the echo has not been received
- // it avoids displaying a pending message whereas the message has been sent
- if (currentRow.getEvent().getAge() == Event.DUMMY_EVENT_AGE) {
- currentRow.updateEvent(row.getEvent());
- }
+ String eventId = event.eventId;
+ MessageRow currentRow = mEventRowMap.get(eventId);
+
+ if (null != currentRow) {
+ // waiting for echo
+ // the message is displayed as sent event if the echo has not been received
+ // it avoids displaying a pending message whereas the message has been sent
+ if (event.getAge() == Event.DUMMY_EVENT_AGE) {
+ currentRow.updateEvent(event);
+ Log.d(LOG_TAG, "## isSupportedRow() : update the timestamp of " + eventId);
+ } else {
+ Log.e(LOG_TAG, "## isSupportedRow() : the event " + eventId + " has already been received");
}
+ return false;
+ }
- if (TextUtils.equals(row.getEvent().getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) {
- RoomMember roomMember = JsonUtils.toRoomMember(row.getEvent().getContent());
- String membership = roomMember.membership;
+ boolean isSupported = VectorMessagesAdapterHelper.isDisplayableEvent(mContext, row);
- if (PreferencesManager.hideJoinLeaveMessages(mContext)) {
- isSupported = !TextUtils.equals(membership, RoomMember.MEMBERSHIP_LEAVE) && !TextUtils.equals(membership, RoomMember.MEMBERSHIP_JOIN);
- }
+ if (isSupported && TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) {
+ RoomMember roomMember = JsonUtils.toRoomMember(event.getContent());
+ String membership = roomMember.membership;
- if (isSupported && PreferencesManager.hideAvatarDisplayNameChangeMessages(mContext) && TextUtils.equals(membership, RoomMember.MEMBERSHIP_JOIN)) {
- EventContent eventContent = JsonUtils.toEventContent(row.getEvent().getContentAsJsonObject());
- EventContent prevEventContent = row.getEvent().getPrevContent();
+ if (PreferencesManager.hideJoinLeaveMessages(mContext)) {
+ isSupported = !TextUtils.equals(membership, RoomMember.MEMBERSHIP_LEAVE) && !TextUtils.equals(membership, RoomMember.MEMBERSHIP_JOIN);
+ }
- String senderDisplayName = eventContent.displayname;
- String prevUserDisplayName = null;
- String avatar = eventContent.avatar_url;
- String prevAvatar = null;
+ if (isSupported && PreferencesManager.hideAvatarDisplayNameChangeMessages(mContext) && TextUtils.equals(membership, RoomMember.MEMBERSHIP_JOIN)) {
+ EventContent eventContent = JsonUtils.toEventContent(event.getContentAsJsonObject());
+ EventContent prevEventContent = event.getPrevContent();
- if ((null != prevEventContent)) {
- prevUserDisplayName = prevEventContent.displayname;
- prevAvatar = prevEventContent.avatar_url;
- }
+ String senderDisplayName = eventContent.displayname;
+ String prevUserDisplayName = null;
+ String avatar = eventContent.avatar_url;
+ String prevAvatar = null;
- // !Updated display name && same avatar
- isSupported = TextUtils.equals(prevUserDisplayName, senderDisplayName) && TextUtils.equals(avatar, prevAvatar);
+ if ((null != prevEventContent)) {
+ prevUserDisplayName = prevEventContent.displayname;
+ prevAvatar = prevEventContent.avatar_url;
}
+
+ // !Updated display name && same avatar
+ isSupported = TextUtils.equals(prevUserDisplayName, senderDisplayName) && TextUtils.equals(avatar, prevAvatar);
}
}
@@ -1731,7 +1824,7 @@ String headerMessage(int position) {
* @param contentView the cell view.
* @param event the linked event
*/
- private void manageSelectionMode(final View contentView, final Event event) {
+ private void manageSelectionMode(final View contentView, final Event event, final int msgType) {
final String eventId = event.eventId;
boolean isInSelectionMode = (null != mSelectedEventId);
@@ -1746,6 +1839,11 @@ private void manageSelectionMode(final View contentView, final Event event) {
contentView.findViewById(R.id.messagesAdapter_body_view).setAlpha(alpha);
contentView.findViewById(R.id.messagesAdapter_avatars_list).setAlpha(alpha);
+ View urlsPreviewView = contentView.findViewById(R.id.messagesAdapter_urls_preview_list);
+ if (null != urlsPreviewView) {
+ urlsPreviewView.setAlpha(alpha);
+ }
+
TextView tsTextView = contentView.findViewById(R.id.messagesAdapter_timestamp);
if (isInSelectionMode && isSelected) {
tsTextView.setVisibility(View.VISIBLE);
@@ -1756,7 +1854,7 @@ private void manageSelectionMode(final View contentView, final Event event) {
@Override
public void onClick(View v) {
if (TextUtils.equals(eventId, mSelectedEventId)) {
- onMessageClick(event, getEventText(contentView), contentView.findViewById(R.id.messagesAdapter_action_anchor));
+ onMessageClick(event, getEventText(contentView, event, msgType), contentView.findViewById(R.id.messagesAdapter_action_anchor));
} else {
onEventTap(eventId);
}
@@ -1767,7 +1865,7 @@ public void onClick(View v) {
@Override
public boolean onLongClick(View v) {
if (!mIsSearchMode) {
- onMessageClick(event, getEventText(contentView), contentView.findViewById(R.id.messagesAdapter_action_anchor));
+ onMessageClick(event, getEventText(contentView, event, msgType), contentView.findViewById(R.id.messagesAdapter_action_anchor));
mSelectedEventId = eventId;
notifyDataSetChanged();
return true;
@@ -1801,14 +1899,19 @@ boolean mergeView(Event event, int position, boolean shouldBeMerged) {
* @param contentView the cell view
* @return the displayed text.
*/
- private String getEventText(View contentView) {
+ private String getEventText(View contentView, Event event, int msgType) {
String text = null;
if (null != contentView) {
- TextView bodyTextView = contentView.findViewById(R.id.messagesAdapter_body);
+ if ((ROW_TYPE_CODE == msgType) || (ROW_TYPE_TEXT == msgType)) {
+ final Message message = JsonUtils.toMessage(event.getContent());
+ text = message.body;
+ } else {
+ TextView bodyTextView = contentView.findViewById(R.id.messagesAdapter_body);
- if (null != bodyTextView) {
- text = bodyTextView.getText().toString();
+ if (null != bodyTextView) {
+ text = bodyTextView.getText().toString();
+ }
}
}
@@ -1822,7 +1925,7 @@ private String getEventText(View contentView) {
* @param contentView the main message view
* @param position the item position
*/
- private void addContentViewListeners(final View convertView, final View contentView, final int position) {
+ private void addContentViewListeners(final View convertView, final View contentView, final int position, final int msgType) {
contentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -1844,7 +1947,7 @@ public boolean onLongClick(View v) {
Event event = row.getEvent();
if (!mIsSearchMode) {
- onMessageClick(event, getEventText(contentView), convertView.findViewById(R.id.messagesAdapter_action_anchor));
+ onMessageClick(event, getEventText(contentView, event, msgType), convertView.findViewById(R.id.messagesAdapter_action_anchor));
mSelectedEventId = event.eventId;
notifyDataSetChanged();
return true;
@@ -2023,6 +2126,15 @@ public void setReadMarkerListener(final ReadMarkerListener listener) {
mReadMarkerListener = listener;
}
+ /**
+ * Set a image getter
+ *
+ * @param imageGetter the image getter
+ */
+ public void setImageGetter(VectorImageGetter imageGetter) {
+ mHelper.setImageGetter(imageGetter);
+ }
+
/**
* Animate a read marker view
*/
@@ -2267,7 +2379,7 @@ private void onMessageClick(final Event event, final String textMsg, final View
Message message = JsonUtils.toMessage(event.getContentAsJsonObject());
// share / forward the message
- menu.findItem(R.id.ic_action_vector_share).setVisible(true);
+ menu.findItem(R.id.ic_action_vector_share).setVisible(!mIsRoomEncrypted);
menu.findItem(R.id.ic_action_vector_forward).setVisible(true);
// save the media in the downloads directory
@@ -2467,4 +2579,4 @@ private void checkEventGroupsMerge(MessageRow deletedRow, int position) {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterHelper.java b/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterHelper.java
index 947a0edbce..3aaaee2884 100755
--- a/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterHelper.java
+++ b/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterHelper.java
@@ -33,6 +33,7 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.gson.JsonNull;
@@ -40,10 +41,16 @@
import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.adapters.MessageRow;
+import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.data.RoomState;
import org.matrix.androidsdk.data.store.IMXStore;
+import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.Event;
-import org.matrix.androidsdk.rest.model.Message;
+import org.matrix.androidsdk.rest.model.MatrixError;
+import org.matrix.androidsdk.rest.model.URLPreview;
+import org.matrix.androidsdk.rest.model.group.Group;
+import org.matrix.androidsdk.rest.model.group.GroupProfile;
+import org.matrix.androidsdk.rest.model.message.Message;
import org.matrix.androidsdk.rest.model.ReceiptData;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.util.EventDisplay;
@@ -54,20 +61,25 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import im.vector.R;
+import im.vector.VectorApp;
import im.vector.listeners.IMessagesAdapterActionsListener;
import im.vector.util.MatrixLinkMovementMethod;
import im.vector.util.MatrixURLSpan;
import im.vector.util.PreferencesManager;
import im.vector.util.RiotEventDisplay;
import im.vector.util.ThemeUtils;
+import im.vector.util.VectorImageGetter;
import im.vector.util.VectorUtils;
import im.vector.view.PillView;
+import im.vector.view.UrlPreviewView;
import im.vector.widgets.WidgetsManager;
/**
@@ -76,14 +88,30 @@
class VectorMessagesAdapterHelper {
private static final String LOG_TAG = VectorMessagesAdapterHelper.class.getSimpleName();
+ /**
+ * Enable multiline mode, split on ...
and retain those delimiters in
+ * the returned fenced block.
+ */
+ public static final String START_FENCED_BLOCK = "";
+ public static final String END_FENCED_BLOCK = "
";
+ private static final Pattern FENCED_CODE_BLOCK_PATTERN = Pattern.compile("(?m)(?=)|(?<=
)");
+
private IMessagesAdapterActionsListener mEventsListener;
- private final MXSession mSession;
+
private final Context mContext;
+ private final MXSession mSession;
+ private final VectorMessagesAdapter mAdapter;
+ private Room mRoom = null;
+
private MatrixLinkMovementMethod mLinkMovementMethod;
- VectorMessagesAdapterHelper(Context context, MXSession session) {
+ private VectorImageGetter mImageGetter;
+
+
+ VectorMessagesAdapterHelper(Context context, MXSession session, VectorMessagesAdapter adapter) {
mContext = context;
mSession = session;
+ mAdapter = adapter;
}
/**
@@ -95,7 +123,6 @@ void setVectorMessagesAdapterActionsListener(IMessagesAdapterActionsListener lis
mEventsListener = listener;
}
-
/**
* Define the links movement method
*
@@ -105,6 +132,15 @@ void setLinkMovementMethod(MatrixLinkMovementMethod method) {
mLinkMovementMethod = method;
}
+ /**
+ * Set the image getter.
+ *
+ * @param imageGetter the image getter
+ */
+ void setImageGetter(VectorImageGetter imageGetter) {
+ mImageGetter = imageGetter;
+ }
+
/**
* Returns an user display name for an user Id.
*
@@ -130,12 +166,15 @@ public static String getUserDisplayName(String userId, RoomState roomState) {
public void setSenderValue(View convertView, MessageRow row, boolean isMergedView) {
// manage sender text
TextView senderTextView = convertView.findViewById(R.id.messagesAdapter_sender);
+ View groupFlairView = convertView.findViewById(R.id.messagesAdapter_flair_groups_list);
if (null != senderTextView) {
Event event = row.getEvent();
if (isMergedView) {
senderTextView.setVisibility(View.GONE);
+ groupFlairView.setVisibility(View.GONE);
+ groupFlairView.setTag(null);
} else {
String eventType = event.getType();
@@ -149,6 +188,8 @@ public void setSenderValue(View convertView, MessageRow row, boolean isMergedVie
Event.EVENT_TYPE_STATE_HISTORY_VISIBILITY.equals(eventType) ||
Event.EVENT_TYPE_MESSAGE_ENCRYPTION.equals(eventType)) {
senderTextView.setVisibility(View.GONE);
+ groupFlairView.setVisibility(View.GONE);
+ groupFlairView.setTag(null);
} else {
senderTextView.setVisibility(View.VISIBLE);
senderTextView.setText(getUserDisplayName(event.getSender(), row.getRoomState()));
@@ -164,8 +205,189 @@ public void onClick(View v) {
}
}
});
+
+ refreshGroupFlairView(groupFlairView, event);
+ }
+ }
+ }
+ }
+
+ /**
+ * Refresh the flairs group view
+ *
+ * @param groupFlairView the flairs view
+ * @param event the event
+ * @param groupIdsSet the groupids
+ * @param tag the tag
+ */
+ private void refreshGroupFlairView(final View groupFlairView, final Event event, final Set groupIdsSet, final String tag) {
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : " + event.sender + " allows flair to " + groupIdsSet);
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : room related groups " + mRoom.getLiveState().getRelatedGroups());
+
+ if (!groupIdsSet.isEmpty()) {
+ // keeps only the intersections
+ groupIdsSet.retainAll(mRoom.getLiveState().getRelatedGroups());
+ }
+
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : group ids to display " + groupIdsSet);
+
+ if (groupIdsSet.isEmpty()) {
+ groupFlairView.setVisibility(View.GONE);
+ } else {
+
+ if (!mSession.isAlive()) {
+ return;
+ }
+
+ groupFlairView.setVisibility(View.VISIBLE);
+
+ ArrayList imageViews = new ArrayList<>();
+
+ imageViews.add((ImageView) (groupFlairView.findViewById(R.id.message_avatar_group_1).findViewById(R.id.avatar_img)));
+ imageViews.add((ImageView) (groupFlairView.findViewById(R.id.message_avatar_group_2).findViewById(R.id.avatar_img)));
+ imageViews.add((ImageView) (groupFlairView.findViewById(R.id.message_avatar_group_3).findViewById(R.id.avatar_img)));
+
+ TextView moreText = groupFlairView.findViewById(R.id.message_more_than_expected);
+
+ final List groupIds = new ArrayList<>(groupIdsSet);
+ int index = 0;
+ int bound = Math.min(groupIds.size(), imageViews.size());
+
+ for (; index < bound; index++) {
+ final String groupId = groupIds.get(index);
+ final ImageView imageView = imageViews.get(index);
+
+ imageView.setVisibility(View.VISIBLE);
+
+ Group group = mSession.getGroupsManager().getGroup(groupId);
+
+ if (null == group) {
+ group = new Group(groupId);
+ }
+
+ GroupProfile cachedGroupProfile = mSession.getGroupsManager().getGroupProfile(groupId);
+
+ if (null != cachedGroupProfile) {
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : profile of " + groupId + " is cached");
+ group.setGroupProfile(cachedGroupProfile);
+ VectorUtils.loadGroupAvatar(mContext, mSession, imageView, group);
+ } else {
+ VectorUtils.loadGroupAvatar(mContext, mSession, imageView, group);
+
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : get profile of " + groupId);
+
+ mSession.getGroupsManager().getGroupProfile(groupId, new ApiCallback() {
+ private void refresh(GroupProfile profile) {
+ if (TextUtils.equals((String) groupFlairView.getTag(), tag)) {
+ Group group = new Group(groupId);
+ group.setGroupProfile(profile);
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : refresh group avatar " + groupId);
+ VectorUtils.loadGroupAvatar(mContext, mSession, imageView, group);
+ }
+ }
+
+ @Override
+ public void onSuccess(GroupProfile groupProfile) {
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : get profile of " + groupId + " succeeded");
+ refresh(groupProfile);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ Log.e(LOG_TAG, "## refreshGroupFlairView () : get profile of " + groupId + " failed " + e.getMessage());
+ refresh(null);
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ Log.e(LOG_TAG, "## refreshGroupFlairView () : get profile of " + groupId + " failed " + e.getMessage());
+ refresh(null);
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ Log.e(LOG_TAG, "## refreshGroupFlairView () : get profile of " + groupId + " failed " + e.getMessage());
+ refresh(null);
+ }
+ });
}
}
+
+ for (; index < imageViews.size(); index++) {
+ imageViews.get(index).setVisibility(View.GONE);
+ }
+
+ moreText.setVisibility((groupIdsSet.size() <= imageViews.size()) ? View.GONE : View.VISIBLE);
+ moreText.setText("+" + (groupIdsSet.size() - imageViews.size()));
+
+ if (groupIdsSet.size() > 0) {
+ groupFlairView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (null != mEventsListener) {
+ mEventsListener.onGroupFlairClick(event.getSender()
+ , groupIds);
+ }
+ }
+ });
+ } else {
+ groupFlairView.setOnClickListener(null);
+ }
+ }
+ }
+
+ /**
+ * Refresh the group flair view
+ *
+ * @param groupFlairView the flairs view
+ * @param event the event
+ */
+ private void refreshGroupFlairView(final View groupFlairView, final Event event) {
+ final String tag = event.getSender() + "__" + event.eventId;
+
+ if (null == mRoom) {
+ mRoom = mSession.getDataHandler().getRoom(event.roomId);
+ }
+
+ // no related groups to this room
+ if (mRoom.getLiveState().getRelatedGroups().isEmpty()) {
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : no related group");
+ groupFlairView.setVisibility(View.GONE);
+ return;
+ }
+
+ groupFlairView.setTag(tag);
+
+ Log.d(LOG_TAG, "## refreshGroupFlairView () : eventId " + event.eventId + " from " + event.sender);
+
+ // cached value first
+ Set userPublicisedGroups = mSession.getGroupsManager().getUserPublicisedGroups(event.getSender());
+
+ if (null != userPublicisedGroups) {
+ refreshGroupFlairView(groupFlairView, event, userPublicisedGroups, tag);
+ } else {
+ groupFlairView.setVisibility(View.GONE);
+ mSession.getGroupsManager().getUserPublicisedGroups(event.getSender(), false, new ApiCallback>() {
+ @Override
+ public void onSuccess(Set groupIdsSet) {
+ refreshGroupFlairView(groupFlairView, event, groupIdsSet, tag);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ Log.e(LOG_TAG, "## refreshGroupFlairView failed " + e.getMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ Log.e(LOG_TAG, "## refreshGroupFlairView failed " + e.getMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ Log.e(LOG_TAG, "## refreshGroupFlairView failed " + e.getMessage());
+ }
+ });
}
}
@@ -506,7 +728,7 @@ static void setMediaProgressLayout(View convertView, View bodyLayoutView) {
}
// cache the pills to avoid compute them again
- Map mPillsCache = new HashMap<>();
+ private Map mPillsDrawableCache = new HashMap<>();
/**
* Trap the clicked URL.
@@ -523,17 +745,31 @@ private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan
int flags = strBuilder.getSpanFlags(span);
if (PillView.isPillable(span.getURL())) {
- String key = span.getURL() + " " + isHighlighted;
- Drawable drawable = mPillsCache.get(key);
+ final String key = span.getURL() + " " + isHighlighted;
+ Drawable drawable = mPillsDrawableCache.get(key);
if (null == drawable) {
- PillView aView = new PillView(mContext);
- aView.setText(strBuilder.subSequence(start, end), span.getURL());
+ final PillView aView = new PillView(mContext);
+ aView.initData(strBuilder.subSequence(start, end), span.getURL(), mSession, new PillView.OnUpdateListener() {
+ @Override
+ public void onAvatarUpdate() {
+ // force to compose
+ aView.setBackgroundResource(android.R.color.transparent);
+
+ // get a drawable from the view
+ Drawable updatedDrawable = aView.getDrawable();
+ mPillsDrawableCache.put(key, updatedDrawable);
+ // should update only the current cell
+ // but it might have been recycled
+ mAdapter.notifyDataSetChanged();
+ }
+ });
aView.setHighlighted(isHighlighted);
drawable = aView.getDrawable();
}
+
if (null != drawable) {
- mPillsCache.put(key, drawable);
+ mPillsDrawableCache.put(key, drawable);
ImageSpan imageSpan = new ImageSpan(drawable);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
strBuilder.setSpan(imageSpan, start, end, flags);
@@ -553,6 +789,59 @@ public void onClick(View view) {
}
}
+ /**
+ * Determine if the message body contains any code blocks.
+ *
+ * @param message the message
+ * @return true if it contains code blocks
+ */
+ boolean containsFencedCodeBlocks(final Message message) {
+ return (null != message.formatted_body) &&
+ message.formatted_body.contains(START_FENCED_BLOCK) &&
+ message.formatted_body.contains(END_FENCED_BLOCK);
+ }
+
+ private Map mCodeBlocksMap = new HashMap<>();
+
+ /**
+ * Split the message body with code blocks delimiters.
+ *
+ * @param message the message
+ * @return the split message body
+ */
+ String[] getFencedCodeBlocks(final Message message) {
+ if (TextUtils.isEmpty(message.formatted_body)) {
+ return new String[0];
+ }
+
+ String[] codeBlocks = mCodeBlocksMap.get(message.formatted_body);
+
+ if (null == codeBlocks) {
+ codeBlocks = FENCED_CODE_BLOCK_PATTERN.split(message.formatted_body);
+ mCodeBlocksMap.put(message.formatted_body, codeBlocks);
+ }
+
+ return codeBlocks;
+ }
+
+ /**
+ * Highlight fenced code
+ *
+ * @param textView the text view
+ */
+ void highlightFencedCode(final TextView textView) {
+ // sanity check
+ if (null == textView) {
+ return;
+ }
+
+ textView.setBackgroundColor(ThemeUtils.getColor(mContext, R.attr.markdown_block_background_color));
+
+ if (null != mLinkMovementMethod) {
+ textView.setMovementMethod(mLinkMovementMethod);
+ }
+ }
+
/**
* Highlight the pattern in the text.
*
@@ -571,8 +860,8 @@ void highlightPattern(TextView textView, Spannable text, String htmlFormattedTex
if (!TextUtils.isEmpty(pattern) && !TextUtils.isEmpty(text) && (text.length() >= pattern.length())) {
- String lowerText = text.toString().toLowerCase();
- String lowerPattern = pattern.toLowerCase();
+ String lowerText = text.toString().toLowerCase(VectorApp.getApplicationLocale());
+ String lowerPattern = pattern.toLowerCase(VectorApp.getApplicationLocale());
int start = 0;
int pos = lowerText.indexOf(lowerPattern, start);
@@ -597,7 +886,7 @@ void highlightPattern(TextView textView, Spannable text, String htmlFormattedTex
// the links are not yet supported by ConsoleHtmlTagHandler
// the markdown tables are not properly supported
- sequence = Html.fromHtml(htmlFormattedText, null, isCustomizable ? htmlTagHandler : null);
+ sequence = Html.fromHtml(htmlFormattedText, mImageGetter, isCustomizable ? htmlTagHandler : null);
// sanity check
if (!TextUtils.isEmpty(sequence)) {
@@ -664,7 +953,7 @@ static boolean isDisplayableEvent(Context context, MessageRow row) {
if (Event.EVENT_TYPE_MESSAGE.equals(eventType)) {
// A message is displayable as long as it has a body
Message message = JsonUtils.toMessage(event.getContent());
- return (message.body != null) && (!message.body.equals(""));
+ return !TextUtils.isEmpty(message.body) || TextUtils.equals(message.msgtype, Message.MSGTYPE_EMOTE);
} else if (Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(eventType)
|| Event.EVENT_TYPE_STATE_ROOM_NAME.equals(eventType)) {
EventDisplay display = new RiotEventDisplay(context, event, roomState);
@@ -721,13 +1010,12 @@ String getSanitisedHtml(final String html) {
return res;
}
- private static final List mAllowedHTMLTags = Arrays.asList(
+ private static final Set mAllowedHTMLTags = new HashSet<>(Arrays.asList(
"font", // custom to matrix for IRC-style font coloring
"del", // for markdown
- // deliberately no h1/h2 to stop people shouting.
- "h3", "h4", "h5", "h6", "blockquote", "p", "a", "ul", "ol",
+ "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "p", "a", "ul", "ol", "sup", "sub",
"nl", "li", "b", "i", "u", "strong", "em", "strike", "code", "hr", "br", "div",
- "table", "thead", "caption", "tbody", "tr", "th", "td", "pre");
+ "table", "thead", "caption", "tbody", "tr", "th", "td", "pre", "span", "img"));
private static final Pattern mHtmlPatter = Pattern.compile("<(\\w+)[^>]*>", Pattern.CASE_INSENSITIVE);
@@ -742,7 +1030,7 @@ private static String sanitiseHTML(final String htmlString) {
String html = htmlString;
Matcher matcher = mHtmlPatter.matcher(htmlString);
- ArrayList tagsToRemove = new ArrayList<>();
+ HashSet tagsToRemove = new HashSet<>();
while (matcher.find()) {
@@ -750,11 +1038,8 @@ private static String sanitiseHTML(final String htmlString) {
String tag = htmlString.substring(matcher.start(1), matcher.end(1));
// test if the tag is not allowed
- if (mAllowedHTMLTags.indexOf(tag) < 0) {
- // add it once
- if (tagsToRemove.indexOf(tag) < 0) {
- tagsToRemove.add(tag);
- }
+ if (!mAllowedHTMLTags.contains(tag)) {
+ tagsToRemove.add(tag);
}
} catch (Exception e) {
Log.e(LOG_TAG, "sanitiseHTML failed " + e.getLocalizedMessage());
@@ -762,12 +1047,16 @@ private static String sanitiseHTML(final String htmlString) {
}
// some tags to remove ?
- if (tagsToRemove.size() > 0) {
+ if (!tagsToRemove.isEmpty()) {
// append the tags to remove
- String tagsToRemoveString = tagsToRemove.get(0);
+ String tagsToRemoveString = "";
- for (int i = 1; i < tagsToRemove.size(); i++) {
- tagsToRemoveString += "|" + tagsToRemove.get(i);
+ for (String tag : tagsToRemove) {
+ if (!tagsToRemoveString.isEmpty()) {
+ tagsToRemoveString += "|";
+ }
+
+ tagsToRemoveString += tag;
}
html = html.replaceAll("<\\/?(" + tagsToRemoveString + ")[^>]*>", "");
@@ -775,4 +1064,127 @@ private static String sanitiseHTML(final String htmlString) {
return html;
}
+
+ /*
+ * *********************************************************************************************
+ * Url preview managements
+ * *********************************************************************************************
+ */
+ private final Map> mExtractedUrls = new HashMap<>();
+ private final Map mUrlsPreview = new HashMap<>();
+ private final Set mPendingUrls = new HashSet<>();
+ private final Set mDismissedPreviews = new HashSet<>();
+
+ /**
+ * Retrieves the webUrl extracted from a text
+ *
+ * @param text the text
+ * @return the web urls list
+ */
+ private List extractWebUrl(String text) {
+ List list = mExtractedUrls.get(text);
+
+ if (null == list) {
+ list = new ArrayList<>();
+
+ Matcher matcher = android.util.Patterns.WEB_URL.matcher(text);
+ while (matcher.find()) {
+ try {
+ String value = text.substring(matcher.start(0), matcher.end(0));
+
+ if (!list.contains(value)) {
+ list.add(value);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## extractWebUrl() " + e.getMessage());
+ }
+ }
+
+ mExtractedUrls.put(text, list);
+ }
+
+ return list;
+ }
+
+ void manageURLPreviews(final Message message, final View convertView, final String id) {
+ LinearLayout urlsPreviewLayout = convertView.findViewById(R.id.messagesAdapter_urls_preview_list);
+
+ // sanity checks
+ if (null == urlsPreviewLayout) {
+ return;
+ }
+
+ //
+ if (TextUtils.isEmpty(message.body)) {
+ urlsPreviewLayout.setVisibility(View.GONE);
+ return;
+ }
+
+ List urls = extractWebUrl(message.body);
+
+ if (urls.isEmpty()) {
+ urlsPreviewLayout.setVisibility(View.GONE);
+ return;
+ }
+
+ // avoid removing items if they are displayed
+ if (TextUtils.equals((String) urlsPreviewLayout.getTag(), id)) {
+ // all the urls have been displayed
+ if (urlsPreviewLayout.getChildCount() == urls.size()) {
+ return;
+ }
+ }
+
+ urlsPreviewLayout.setTag(id);
+
+ // remove url previews
+ while (urlsPreviewLayout.getChildCount() > 0) {
+ urlsPreviewLayout.removeViewAt(0);
+ }
+
+ urlsPreviewLayout.setVisibility(View.VISIBLE);
+
+ for (final String url : urls) {
+ final String downloadKey = url.hashCode() + "---";
+ String displayKey = url + "<----->" + id;
+
+ if (UrlPreviewView.didUrlPreviewDismiss(displayKey)) {
+ Log.d(LOG_TAG, "## manageURLPreviews() : " + displayKey + " has been dismissed");
+ } else if (mPendingUrls.contains(url)) {
+ // please wait
+ } else if (!mUrlsPreview.containsKey(downloadKey)) {
+ mPendingUrls.add(url);
+ mSession.getEventsApiClient().getURLPreview(url, System.currentTimeMillis(), new ApiCallback() {
+ @Override
+ public void onSuccess(URLPreview urlPreview) {
+ mPendingUrls.remove(url);
+
+ if (!mUrlsPreview.containsKey(downloadKey)) {
+ mUrlsPreview.put(downloadKey, urlPreview);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onSuccess(null);
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onSuccess(null);
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onSuccess(null);
+ }
+ });
+ } else {
+ UrlPreviewView previewView = new UrlPreviewView(mContext);
+ previewView.setUrlPreview(mContext, mSession, mUrlsPreview.get(downloadKey), displayKey);
+ urlsPreviewLayout.addView(previewView);
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterMediasHelper.java b/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterMediasHelper.java
index 716936a4e6..4935cb6ca0 100755
--- a/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterMediasHelper.java
+++ b/vector/src/main/java/im/vector/adapters/VectorMessagesAdapterMediasHelper.java
@@ -37,18 +37,21 @@
import org.matrix.androidsdk.listeners.IMXMediaUploadListener;
import org.matrix.androidsdk.listeners.MXMediaDownloadListener;
import org.matrix.androidsdk.listeners.MXMediaUploadListener;
-import org.matrix.androidsdk.rest.model.EncryptedFileInfo;
+import org.matrix.androidsdk.rest.model.crypto.EncryptedFileInfo;
import org.matrix.androidsdk.rest.model.Event;
-import org.matrix.androidsdk.rest.model.FileMessage;
-import org.matrix.androidsdk.rest.model.ImageInfo;
-import org.matrix.androidsdk.rest.model.ImageMessage;
+import org.matrix.androidsdk.rest.model.message.FileMessage;
+import org.matrix.androidsdk.rest.model.message.ImageInfo;
+import org.matrix.androidsdk.rest.model.message.ImageMessage;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.Message;
-import org.matrix.androidsdk.rest.model.VideoInfo;
-import org.matrix.androidsdk.rest.model.VideoMessage;
+import org.matrix.androidsdk.rest.model.message.Message;
+import org.matrix.androidsdk.rest.model.message.VideoInfo;
+import org.matrix.androidsdk.rest.model.message.VideoMessage;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.androidsdk.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+
import im.vector.R;
import im.vector.listeners.IMessagesAdapterActionsListener;
@@ -168,6 +171,10 @@ public void onUploadComplete(final String uploadId, final String contentUri) {
refreshUploadViews(event, uploadStats, uploadProgressLayout);
}
+ // the image / video bitmaps are set to null if the matching URL is not the same
+ // to avoid flickering
+ private Map mUrlByBitmapIndex = new HashMap<>();
+
/**
* Manage the image/video download.
*
@@ -240,8 +247,13 @@ void managePendingImageVideoDownload(final View convertView, final Event event,
ImageView imageView = convertView.findViewById(R.id.messagesAdapter_image);
- // reset the bitmap
- imageView.setImageBitmap(null);
+ // reset the bitmap if the url is not the same than before
+ if ((null == thumbUrl) || !TextUtils.equals(imageView.hashCode() + "", mUrlByBitmapIndex.get(thumbUrl))) {
+ imageView.setImageBitmap(null);
+ if (null != thumbUrl) {
+ mUrlByBitmapIndex.put(thumbUrl, imageView.hashCode() + "");
+ }
+ }
RelativeLayout informationLayout = convertView.findViewById(R.id.messagesAdapter_image_layout);
final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) informationLayout.getLayoutParams();
@@ -397,12 +409,13 @@ void managePendingImageVideoUpload(final View convertView, final Event event, Me
String uploadingUrl;
final boolean isUploadingThumbnail;
+ boolean isUploadingContent = false;
if (isVideoMessage) {
- uploadingUrl = ((VideoMessage) message).info.thumbnail_url;
+ uploadingUrl = ((VideoMessage) message).getThumbnailUrl();
isUploadingThumbnail = ((VideoMessage) message).isThumbnailLocalContent();
} else {
- uploadingUrl = ((ImageMessage) message).info.thumbnailUrl;
+ uploadingUrl = ((ImageMessage) message).getThumbnailUrl();
isUploadingThumbnail = ((ImageMessage) message).isThumbnailLocalContent();
}
@@ -412,10 +425,14 @@ void managePendingImageVideoUpload(final View convertView, final Event event, Me
progress = mSession.getMediasCache().getProgressValueForUploadId(uploadingUrl);
} else {
if (isVideoMessage) {
- uploadingUrl = ((VideoMessage) message).url;
+ uploadingUrl = ((VideoMessage) message).getUrl();
+ isUploadingContent = ((VideoMessage) message).isLocalContent();
+
} else {
- uploadingUrl = ((ImageMessage) message).url;
+ uploadingUrl = ((ImageMessage) message).getUrl();
+ isUploadingContent = ((ImageMessage) message).isLocalContent();
}
+
progress = mSession.getMediasCache().getProgressValueForUploadId(uploadingUrl);
}
@@ -478,11 +495,12 @@ public void onUploadComplete(final String uploadId, final String contentUri) {
uploadSpinner.setVisibility(((progress < 0) && event.isSending()) ? View.VISIBLE : View.GONE);
refreshUploadViews(event, mSession.getMediasCache().getStatsForUploadId(uploadingUrl), uploadProgressLayout);
- if (!isUploadingThumbnail) {
+ if (isUploadingContent) {
progress = 10 + (progress * 90 / 100);
- } else {
+ } else if (isUploadingThumbnail) {
progress = (progress * 10 / 100);
}
+
updateUploadProgress(uploadProgressLayout, progress);
uploadProgressLayout.setVisibility(((progress >= 0) && event.isSending()) ? View.VISIBLE : View.GONE);
}
diff --git a/vector/src/main/java/im/vector/adapters/VectorParticipantsAdapter.java b/vector/src/main/java/im/vector/adapters/VectorParticipantsAdapter.java
index 429923394a..d2495c42ad 100755
--- a/vector/src/main/java/im/vector/adapters/VectorParticipantsAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorParticipantsAdapter.java
@@ -40,7 +40,7 @@
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.RoomMember;
-import org.matrix.androidsdk.rest.model.Search.SearchUsersResponse;
+import org.matrix.androidsdk.rest.model.search.SearchUsersResponse;
import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.util.Log;
@@ -55,6 +55,7 @@
import im.vector.Matrix;
import im.vector.R;
+import im.vector.VectorApp;
import im.vector.activity.CommonActivityUtils;
import im.vector.contacts.Contact;
import im.vector.contacts.ContactsManager;
@@ -182,7 +183,7 @@ public void setSearchedPattern(String pattern, ParticipantAdapterItem firstEntry
if (null == pattern) {
pattern = "";
} else {
- pattern = pattern.toLowerCase().trim().toLowerCase();
+ pattern = pattern.toLowerCase().trim().toLowerCase(VectorApp.getApplicationLocale());
}
if (!pattern.equals(mPattern) || TextUtils.isEmpty(mPattern)) {
@@ -306,7 +307,7 @@ private void listOtherMembers() {
iterator.remove();
} else if (!TextUtils.isEmpty(item.mDisplayName)) {
// Add to the display names list
- displayNamesList.add(item.mDisplayName.toLowerCase());
+ displayNamesList.add(item.mDisplayName.toLowerCase(VectorApp.getApplicationLocale()));
}
}
@@ -433,14 +434,15 @@ public void onSuccess(SearchUsersResponse searchUsersResponse) {
mIsOfflineContactsSearch = false;
mKnownContactsLimited = (null != searchUsersResponse.limited) ? searchUsersResponse.limited : false;
- onKnownContactsSearchEnd(participantItemList, theFirstEntry, searchListener);
+
+ searchAccountKnownContacts(theFirstEntry, participantItemList, false, searchListener);
}
}
private void onError() {
if (TextUtils.equals(fPattern, mPattern)) {
mIsOfflineContactsSearch = true;
- searchAccountKnownContacts(theFirstEntry, searchListener);
+ searchAccountKnownContacts(theFirstEntry, new ArrayList(), true, searchListener);
}
}
@@ -460,19 +462,19 @@ public void onUnexpectedError(Exception e) {
}
});
} else {
- searchAccountKnownContacts(theFirstEntry, searchListener);
+ searchAccountKnownContacts(theFirstEntry, new ArrayList(), true, searchListener);
}
}
/**
* Search the known contacts from the account known users list.
*
- * @param theFirstEntry the adapter first entry
- * @param searchListener the listener
+ * @param theFirstEntry the adapter first entry
+ * @param participantItemList the participants initial list
+ * @param sortRoomContactsList true to sort the room contacts list
+ * @param searchListener the listener
*/
- private void searchAccountKnownContacts(final ParticipantAdapterItem theFirstEntry, final OnParticipantsSearchListener searchListener) {
- List participantItemList = new ArrayList<>();
-
+ private void searchAccountKnownContacts(final ParticipantAdapterItem theFirstEntry, final List participantItemList, final boolean sortRoomContactsList, final OnParticipantsSearchListener searchListener) {
// the list is not anymore limited
mKnownContactsLimited = false;
@@ -490,7 +492,7 @@ public void run() {
handler.post(new Runnable() {
@Override
public void run() {
- searchAccountKnownContacts(theFirstEntry, searchListener);
+ searchAccountKnownContacts(theFirstEntry, participantItemList, sortRoomContactsList, searchListener);
}
});
}
@@ -570,7 +572,7 @@ public void run() {
}
}
- onKnownContactsSearchEnd(participantItemList, theFirstEntry, searchListener);
+ onKnownContactsSearchEnd(participantItemList, theFirstEntry, sortRoomContactsList, searchListener);
}
/**
@@ -579,9 +581,10 @@ public void run() {
*
* @param participantItemList the known contacts list
* @param theFirstEntry the adapter first entry
+ * @param sort true to sort participantItemList
* @param searchListener the search listener
*/
- private void onKnownContactsSearchEnd(List participantItemList, final ParticipantAdapterItem theFirstEntry, final OnParticipantsSearchListener searchListener) {
+ private void onKnownContactsSearchEnd(List participantItemList, final ParticipantAdapterItem theFirstEntry, final boolean sort, final OnParticipantsSearchListener searchListener) {
// ensure that the PIDs have been retrieved
// it might have failed
ContactsManager.getInstance().retrievePids();
@@ -655,7 +658,7 @@ private void onKnownContactsSearchEnd(List participantIt
}
if (!TextUtils.isEmpty(mPattern)) {
- if (roomContactsList.size() > 0) {
+ if ((roomContactsList.size() > 0) && sort) {
Collections.sort(roomContactsList, mSortMethod);
}
mParticipantsListsList.add(roomContactsList);
diff --git a/vector/src/main/java/im/vector/adapters/VectorPublicRoomsAdapter.java b/vector/src/main/java/im/vector/adapters/VectorPublicRoomsAdapter.java
index b8c74aa48c..fa2f7d7a14 100644
--- a/vector/src/main/java/im/vector/adapters/VectorPublicRoomsAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorPublicRoomsAdapter.java
@@ -29,7 +29,7 @@
import im.vector.util.VectorUtils;
import org.matrix.androidsdk.MXSession;
-import org.matrix.androidsdk.rest.model.PublicRoom;
+import org.matrix.androidsdk.rest.model.publicroom.PublicRoom;
/**
* An adapter which can display m.room.member content.
diff --git a/vector/src/main/java/im/vector/adapters/VectorRoomCreationAdapter.java b/vector/src/main/java/im/vector/adapters/VectorRoomCreationAdapter.java
index 1543af6bbb..39c753d372 100755
--- a/vector/src/main/java/im/vector/adapters/VectorRoomCreationAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorRoomCreationAdapter.java
@@ -37,6 +37,7 @@
import im.vector.Matrix;
import im.vector.R;
+import im.vector.VectorApp;
import im.vector.util.VectorUtils;
/**
@@ -102,7 +103,7 @@ public void notifyDataSetChanged() {
ParticipantAdapterItem item = getItem(i);
if (!TextUtils.isEmpty(item.mDisplayName)) {
- mDisplayNamesList.add(item.mDisplayName.toLowerCase());
+ mDisplayNamesList.add(item.mDisplayName.toLowerCase(VectorApp.getApplicationLocale()));
}
}
}
diff --git a/vector/src/main/java/im/vector/adapters/VectorRoomDetailsMembersAdapter.java b/vector/src/main/java/im/vector/adapters/VectorRoomDetailsMembersAdapter.java
index f0a7f90a9b..8f3a986864 100644
--- a/vector/src/main/java/im/vector/adapters/VectorRoomDetailsMembersAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorRoomDetailsMembersAdapter.java
@@ -37,7 +37,7 @@
import org.matrix.androidsdk.db.MXMediasCache;
import org.matrix.androidsdk.rest.model.PowerLevels;
import org.matrix.androidsdk.rest.model.RoomMember;
-import org.matrix.androidsdk.rest.model.RoomThirdPartyInvite;
+import org.matrix.androidsdk.rest.model.pid.RoomThirdPartyInvite;
import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.util.Log;
@@ -48,6 +48,7 @@
import java.util.HashMap;
import im.vector.R;
+import im.vector.VectorApp;
import im.vector.activity.CommonActivityUtils;
import im.vector.util.ThemeUtils;
import im.vector.util.VectorUtils;
@@ -208,7 +209,7 @@ public void setSearchedPattern(String aPattern, final OnRoomMembersSearchListene
} else {
// new pattern different from previous one?
if (!aPattern.trim().equals(mSearchPattern) || aIsRefreshForced) {
- mSearchPattern = aPattern.trim().toLowerCase();
+ mSearchPattern = aPattern.trim().toLowerCase(VectorApp.getApplicationLocale());
updateRoomMembersDataModel(searchListener);
} else {
// search pattern is identical, notify listener and exit
diff --git a/vector/src/main/java/im/vector/adapters/VectorRoomSummaryAdapter.java b/vector/src/main/java/im/vector/adapters/VectorRoomSummaryAdapter.java
index 47dedd2178..9e05a21443 100644
--- a/vector/src/main/java/im/vector/adapters/VectorRoomSummaryAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorRoomSummaryAdapter.java
@@ -50,6 +50,7 @@
import im.vector.Matrix;
import im.vector.PublicRoomsManager;
import im.vector.R;
+import im.vector.VectorApp;
import im.vector.util.RiotEventDisplay;
import im.vector.util.RoomUtils;
import im.vector.util.ThemeUtils;
@@ -222,7 +223,7 @@ private boolean isMatchedPattern(Room room) {
if (!TextUtils.isEmpty(mSearchedPattern)) {
String roomName = VectorUtils.getRoomDisplayName(mContext, mMxSession, room);
- res = (!TextUtils.isEmpty(roomName) && (roomName.toLowerCase().contains(mSearchedPattern)));
+ res = (!TextUtils.isEmpty(roomName) && (roomName.toLowerCase(VectorApp.getApplicationLocale()).contains(mSearchedPattern)));
}
return res;
@@ -953,7 +954,7 @@ public void setSearchPattern(String pattern) {
String trimmedPattern = pattern;
if (null != pattern) {
- trimmedPattern = pattern.trim().toLowerCase();
+ trimmedPattern = pattern.trim().toLowerCase(VectorApp.getApplicationLocale());
trimmedPattern = TextUtils.getTrimmedLength(trimmedPattern) == 0 ? null : trimmedPattern;
}
diff --git a/vector/src/main/java/im/vector/adapters/VectorSearchFilesListAdapter.java b/vector/src/main/java/im/vector/adapters/VectorSearchFilesListAdapter.java
index 9fb9a64150..896fe3b770 100755
--- a/vector/src/main/java/im/vector/adapters/VectorSearchFilesListAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorSearchFilesListAdapter.java
@@ -29,12 +29,12 @@
import org.matrix.androidsdk.adapters.MessageRow;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.db.MXMediasCache;
-import org.matrix.androidsdk.rest.model.EncryptedFileInfo;
+import org.matrix.androidsdk.rest.model.crypto.EncryptedFileInfo;
import org.matrix.androidsdk.rest.model.Event;
-import org.matrix.androidsdk.rest.model.FileMessage;
-import org.matrix.androidsdk.rest.model.ImageMessage;
-import org.matrix.androidsdk.rest.model.Message;
-import org.matrix.androidsdk.rest.model.VideoMessage;
+import org.matrix.androidsdk.rest.model.message.FileMessage;
+import org.matrix.androidsdk.rest.model.message.ImageMessage;
+import org.matrix.androidsdk.rest.model.message.Message;
+import org.matrix.androidsdk.rest.model.message.VideoMessage;
import org.matrix.androidsdk.util.JsonUtils;
import im.vector.R;
diff --git a/vector/src/main/java/im/vector/adapters/VectorSearchMessagesListAdapter.java b/vector/src/main/java/im/vector/adapters/VectorSearchMessagesListAdapter.java
index e1e8f52102..3ee94b8bd5 100755
--- a/vector/src/main/java/im/vector/adapters/VectorSearchMessagesListAdapter.java
+++ b/vector/src/main/java/im/vector/adapters/VectorSearchMessagesListAdapter.java
@@ -32,6 +32,7 @@
import org.matrix.androidsdk.db.MXMediasCache;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.util.EventDisplay;
+import org.matrix.androidsdk.util.Log;
import im.vector.R;
import im.vector.util.RiotEventDisplay;
@@ -41,6 +42,7 @@
* An adapter which display a list of messages found after a search
*/
public class VectorSearchMessagesListAdapter extends VectorMessagesAdapter {
+ private static final String LOG_TAG = VectorSearchMessagesListAdapter.class.getSimpleName();
// display the room name in the result view
private final boolean mDisplayRoomName;
@@ -57,6 +59,7 @@ public VectorSearchMessagesListAdapter(MXSession session, Context context, boole
R.layout.adapter_item_vector_search_message_image_video,
-1,
R.layout.adapter_item_vector_search_message_emoji,
+ R.layout.adapter_item_vector_message_code,
mediasCache);
setNotifyOnChange(true);
@@ -87,110 +90,113 @@ protected boolean supportMessageRowMerge(MessageRow row) {
public View getView(int position, View convertView2, ViewGroup parent) {
View convertView = super.getView(position, convertView2, parent);
- MessageRow row = getItem(position);
- Event event = row.getEvent();
-
- // some items are always hidden
- convertView.findViewById(R.id.messagesAdapter_avatars_list).setVisibility(View.GONE);
- convertView.findViewById(R.id.messagesAdapter_message_separator).setVisibility(View.GONE);
- convertView.findViewById(R.id.messagesAdapter_action_image).setVisibility(View.GONE);
- convertView.findViewById(R.id.messagesAdapter_top_margin_when_no_room_name).setVisibility(mDisplayRoomName ? View.GONE : View.VISIBLE);
- convertView.findViewById(R.id.messagesAdapter_message_header).setVisibility(View.GONE);
+ try {
+ MessageRow row = getItem(position);
+ Event event = row.getEvent();
- Room room = mSession.getDataHandler().getStore().getRoom(event.roomId);
+ // some items are always hidden
+ convertView.findViewById(R.id.messagesAdapter_avatars_list).setVisibility(View.GONE);
+ convertView.findViewById(R.id.messagesAdapter_message_separator).setVisibility(View.GONE);
+ convertView.findViewById(R.id.messagesAdapter_action_image).setVisibility(View.GONE);
+ convertView.findViewById(R.id.messagesAdapter_top_margin_when_no_room_name).setVisibility(mDisplayRoomName ? View.GONE : View.VISIBLE);
+ convertView.findViewById(R.id.messagesAdapter_message_header).setVisibility(View.GONE);
- RoomState roomState = row.getRoomState();
+ Room room = mSession.getDataHandler().getStore().getRoom(event.roomId);
- if (null == roomState) {
- roomState = room.getLiveState();
- }
+ RoomState roomState = row.getRoomState();
+ if (null == roomState) {
+ roomState = room.getLiveState();
+ }
- // refresh the avatar
- ImageView avatarView = convertView.findViewById(R.id.messagesAdapter_roundAvatar).findViewById(R.id.avatar_img);
- mHelper.loadMemberAvatar(avatarView, row);
+ // refresh the avatar
+ ImageView avatarView = convertView.findViewById(R.id.messagesAdapter_roundAvatar).findViewById(R.id.avatar_img);
+ mHelper.loadMemberAvatar(avatarView, row);
- // display the sender
- TextView senderTextView = convertView.findViewById(R.id.messagesAdapter_sender);
- if (senderTextView != null) {
- senderTextView.setText(VectorMessagesAdapterHelper.getUserDisplayName(event.getSender(), roomState));
- }
+ // display the sender
+ TextView senderTextView = convertView.findViewById(R.id.messagesAdapter_sender);
+ if (senderTextView != null) {
+ senderTextView.setText(VectorMessagesAdapterHelper.getUserDisplayName(event.getSender(), roomState));
+ }
- // display the body
- TextView bodyTextView = convertView.findViewById(R.id.messagesAdapter_body);
- // set the message text
- EventDisplay display = new RiotEventDisplay(mContext, event, (null != room) ? room.getLiveState() : null);
- CharSequence text = display.getTextualDisplay();
+ // display the body
+ TextView bodyTextView = convertView.findViewById(R.id.messagesAdapter_body);
+ // set the message text
+ EventDisplay display = new RiotEventDisplay(mContext, event, (null != room) ? room.getLiveState() : null);
+ CharSequence text = display.getTextualDisplay();
- if (null == text) {
- text = "";
- }
+ if (null == text) {
+ text = "";
+ }
- try {
- highlightPattern(bodyTextView, new SpannableString(text), mPattern);
- } catch (Exception e) {
- // an exception might be triggered with HTML content
- // Indeed, the formatting can fail because of the single line display.
- // in this case, the formatting is ignored.
- bodyTextView.setText(text.toString());
- }
+ try {
+ highlightPattern(bodyTextView, new SpannableString(text), mPattern);
+ } catch (Exception e) {
+ // an exception might be triggered with HTML content
+ // Indeed, the formatting can fail because of the single line display.
+ // in this case, the formatting is ignored.
+ bodyTextView.setText(text.toString());
+ }
- // display timestamp
- TextView timeTextView = convertView.findViewById(R.id.messagesAdapter_timestamp);
- timeTextView.setText(AdapterUtils.tsToString(mContext, event.getOriginServerTs(), true));
+ // display timestamp
+ TextView timeTextView = convertView.findViewById(R.id.messagesAdapter_timestamp);
+ timeTextView.setText(AdapterUtils.tsToString(mContext, event.getOriginServerTs(), true));
- // display the room name
- View roomNameLayout = convertView.findViewById(R.id.messagesAdapter_message_room_name_layout);
- roomNameLayout.setVisibility(mDisplayRoomName ? View.VISIBLE : View.GONE);
+ // display the room name
+ View roomNameLayout = convertView.findViewById(R.id.messagesAdapter_message_room_name_layout);
+ roomNameLayout.setVisibility(mDisplayRoomName ? View.VISIBLE : View.GONE);
- if (mDisplayRoomName) {
- TextView roomTextView = convertView.findViewById(R.id.messagesAdapter_message_room_name_textview);
- roomTextView.setText(VectorUtils.getRoomDisplayName(mContext, mSession, room));
- }
+ if (mDisplayRoomName) {
+ TextView roomTextView = convertView.findViewById(R.id.messagesAdapter_message_room_name_textview);
+ roomTextView.setText(VectorUtils.getRoomDisplayName(mContext, mSession, room));
+ }
- // display the day
- View dayLayout = convertView.findViewById(R.id.messagesAdapter_search_message_day_separator);
+ // display the day
+ View dayLayout = convertView.findViewById(R.id.messagesAdapter_search_message_day_separator);
- // day separator
- String headerMessage = headerMessage(position);
+ // day separator
+ String headerMessage = headerMessage(position);
- if (!TextUtils.isEmpty(headerMessage)) {
- dayLayout.setVisibility(View.VISIBLE);
+ if (!TextUtils.isEmpty(headerMessage)) {
+ dayLayout.setVisibility(View.VISIBLE);
- TextView headerText = convertView.findViewById(R.id.messagesAdapter_message_header_text);
- headerText.setText(headerMessage);
+ TextView headerText = convertView.findViewById(R.id.messagesAdapter_message_header_text);
+ headerText.setText(headerMessage);
- dayLayout.findViewById(R.id.messagesAdapter_message_header_top_margin).setVisibility(View.GONE);
- dayLayout.findViewById(R.id.messagesAdapter_message_header_bottom_margin).setVisibility(View.GONE);
- } else {
- dayLayout.setVisibility(View.GONE);
- }
+ dayLayout.findViewById(R.id.messagesAdapter_message_header_top_margin).setVisibility(View.GONE);
+ dayLayout.findViewById(R.id.messagesAdapter_message_header_bottom_margin).setVisibility(View.GONE);
+ } else {
+ dayLayout.setVisibility(View.GONE);
+ }
- // message separator is only displayed when a message is not the last message in a day section
- convertView.findViewById(R.id.messagesAdapter_search_separator_line).setVisibility(!TextUtils.isEmpty(headerMessage(position + 1)) ? View.GONE : View.VISIBLE);
+ // message separator is only displayed when a message is not the last message in a day section
+ convertView.findViewById(R.id.messagesAdapter_search_separator_line).setVisibility(!TextUtils.isEmpty(headerMessage(position + 1)) ? View.GONE : View.VISIBLE);
- final int fPosition = position;
+ final int fPosition = position;
- convertView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (null != mVectorMessagesAdapterEventsListener) {
- mVectorMessagesAdapterEventsListener.onContentClick(fPosition);
+ convertView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (null != mVectorMessagesAdapterEventsListener) {
+ mVectorMessagesAdapterEventsListener.onContentClick(fPosition);
+ }
}
- }
- });
+ });
- convertView.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (null != mVectorMessagesAdapterEventsListener) {
- return mVectorMessagesAdapterEventsListener.onContentLongClick(fPosition);
- }
+ convertView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (null != mVectorMessagesAdapterEventsListener) {
+ return mVectorMessagesAdapterEventsListener.onContentLongClick(fPosition);
+ }
- return false;
- }
- });
+ return false;
+ }
+ });
+ } catch (Throwable t) {
+ Log.e(LOG_TAG, "## getView() failed " + t.getMessage());
+ }
return convertView;
}
diff --git a/vector/src/main/java/im/vector/car/CarBroadcastReceiver.java b/vector/src/main/java/im/vector/car/CarBroadcastReceiver.java
index 6cdf49d3f4..638e8b5973 100755
--- a/vector/src/main/java/im/vector/car/CarBroadcastReceiver.java
+++ b/vector/src/main/java/im/vector/car/CarBroadcastReceiver.java
@@ -14,10 +14,10 @@
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.Message;
+import org.matrix.androidsdk.rest.model.message.Message;
import im.vector.Matrix;
-import im.vector.util.NotificationUtils;
+import im.vector.notifications.NotificationUtils;
public class CarBroadcastReceiver extends BroadcastReceiver {
diff --git a/vector/src/main/java/im/vector/contacts/Contact.java b/vector/src/main/java/im/vector/contacts/Contact.java
index 936364ef5d..d29cf177e9 100755
--- a/vector/src/main/java/im/vector/contacts/Contact.java
+++ b/vector/src/main/java/im/vector/contacts/Contact.java
@@ -303,20 +303,20 @@ public boolean contains(String pattern) {
boolean matched = false;
if (!TextUtils.isEmpty(mDisplayName)) {
- matched = (mDisplayName.toLowerCase().contains(pattern));
+ matched = (mDisplayName.toLowerCase(VectorApp.getApplicationLocale()).contains(pattern));
}
if (!matched) {
for (String email : mEmails) {
- matched |= email.toLowerCase().contains(pattern);
+ matched |= email.toLowerCase(VectorApp.getApplicationLocale()).contains(pattern);
}
}
if (!matched) {
for (PhoneNumber pn : mPhoneNumbers) {
- matched |= pn.mMsisdnPhoneNumber.toLowerCase().contains(pattern)
- || pn.mRawPhoneNumber.toLowerCase().contains(pattern)
- || (pn.mE164PhoneNumber != null && pn.mE164PhoneNumber.toLowerCase().contains(pattern));
+ matched |= pn.mMsisdnPhoneNumber.toLowerCase(VectorApp.getApplicationLocale()).contains(pattern)
+ || pn.mRawPhoneNumber.toLowerCase(VectorApp.getApplicationLocale()).contains(pattern)
+ || (pn.mE164PhoneNumber != null && pn.mE164PhoneNumber.toLowerCase(VectorApp.getApplicationLocale()).contains(pattern));
}
}
diff --git a/vector/src/main/java/im/vector/contacts/PIDsRetriever.java b/vector/src/main/java/im/vector/contacts/PIDsRetriever.java
index 0cb8481714..190d29a741 100755
--- a/vector/src/main/java/im/vector/contacts/PIDsRetriever.java
+++ b/vector/src/main/java/im/vector/contacts/PIDsRetriever.java
@@ -26,7 +26,7 @@
import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.ThreePid;
+import org.matrix.androidsdk.rest.model.pid.ThreePid;
import java.util.ArrayList;
import java.util.Collection;
@@ -37,6 +37,7 @@
import java.util.Set;
import im.vector.Matrix;
+import im.vector.VectorApp;
/**
* retrieve the contact matrix IDs
@@ -177,7 +178,7 @@ private Set retrieveMatrixIds(List contacts) {
* @return true if the matrix Ids have been retrieved
*/
public void retrieveMatrixIds(final Context context, final List contacts, final boolean localUpdateOnly) {
- Log.d(LOG_TAG, String.format("retrieveMatrixIds starts for %d contacts", contacts == null ? 0 : contacts.size()));
+ Log.d(LOG_TAG, String.format(VectorApp.getApplicationLocale(), "retrieveMatrixIds starts for %d contacts", contacts == null ? 0 : contacts.size()));
// sanity checks
if ((null == contacts) || (0 == contacts.size())) {
if (null != mListener) {
diff --git a/vector/src/main/java/im/vector/db/VectorContentProvider.java b/vector/src/main/java/im/vector/db/VectorContentProvider.java
index e611f0d470..19eb8abf52 100755
--- a/vector/src/main/java/im/vector/db/VectorContentProvider.java
+++ b/vector/src/main/java/im/vector/db/VectorContentProvider.java
@@ -22,9 +22,10 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
-import android.util.Log;
import android.webkit.MimeTypeMap;
+import org.matrix.androidsdk.util.Log;
+
import java.io.File;
import java.io.FileNotFoundException;
@@ -97,7 +98,7 @@ public int delete(Uri arg0, String arg1, String[] arg2) {
@Override
public String getType(Uri arg0) {
String type = null;
- String extension = MimeTypeMap.getFileExtensionFromUrl(arg0.toString().toLowerCase());
+ String extension = MimeTypeMap.getFileExtensionFromUrl(arg0.toString().toLowerCase(VectorApp.getApplicationLocale()));
if (extension != null) {
MimeTypeMap mime = MimeTypeMap.getSingleton();
type = mime.getMimeTypeFromExtension(extension);
diff --git a/vector/src/main/java/im/vector/fragments/AbsHomeFragment.java b/vector/src/main/java/im/vector/fragments/AbsHomeFragment.java
index 470fcf1e4f..707f80940d 100644
--- a/vector/src/main/java/im/vector/fragments/AbsHomeFragment.java
+++ b/vector/src/main/java/im/vector/fragments/AbsHomeFragment.java
@@ -32,6 +32,7 @@
import org.matrix.androidsdk.data.RoomSummary;
import org.matrix.androidsdk.data.RoomTag;
import org.matrix.androidsdk.rest.callback.ApiCallback;
+import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.util.BingRulesManager;
import org.matrix.androidsdk.util.Log;
@@ -53,7 +54,7 @@
/**
* Abstract fragment providing the universal search
*/
-public abstract class AbsHomeFragment extends Fragment implements AbsAdapter.InvitationListener, AbsAdapter.MoreRoomActionListener, RoomUtils.MoreActionListener {
+public abstract class AbsHomeFragment extends Fragment implements AbsAdapter.RoomInvitationListener, AbsAdapter.MoreRoomActionListener, RoomUtils.MoreActionListener {
private static final String LOG_TAG = AbsHomeFragment.class.getSimpleName();
private static final String CURRENT_FILTER = "CURRENT_FILTER";
@@ -107,7 +108,9 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mActivity = (VectorHomeActivity) getActivity();
+ if (getActivity() instanceof VectorHomeActivity) {
+ mActivity = (VectorHomeActivity) getActivity();
+ }
mSession = Matrix.getInstance(getActivity()).getDefaultSession();
if (savedInstanceState != null && savedInstanceState.containsKey(CURRENT_FILTER)) {
@@ -119,7 +122,7 @@ public void onActivityCreated(final Bundle savedInstanceState) {
@CallSuper
public void onResume() {
super.onResume();
- if (mPrimaryColor != -1) {
+ if ((mPrimaryColor != -1) && (null != mActivity)) {
mActivity.updateTabStyle(mPrimaryColor, mSecondaryColor != -1 ? mSecondaryColor : mPrimaryColor);
}
}
@@ -171,7 +174,7 @@ public void onPreviewRoom(MXSession session, String roomId) {
@Override
public void onRejectInvitation(MXSession session, String roomId) {
Log.i(LOG_TAG, "onRejectInvitation " + roomId);
- mActivity.onRejectInvitation(session, roomId);
+ mActivity.onRejectInvitation(roomId, null);
}
@Override
@@ -184,9 +187,9 @@ public void onMoreActionClick(View itemView, Room room) {
}
@Override
- public void onToggleRoomNotifications(MXSession session, String roomId) {
+ public void onUpdateRoomNotificationsState(MXSession session, String roomId, BingRulesManager.RoomNotificationState state) {
mActivity.showWaitingView();
- RoomUtils.toggleNotifications(session, roomId, new BingRulesManager.onBingRuleUpdateListener() {
+ session.getDataHandler().getBingRulesManager().updateRoomNotificationState(roomId, state, new BingRulesManager.onBingRuleUpdateListener() {
@Override
public void onBingRuleUpdateSuccess() {
onRequestDone(null);
@@ -249,15 +252,36 @@ public void onLeaveRoom(final MXSession session, final String roomId) {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mActivity != null && !mActivity.isFinishing()) {
- mActivity.onRejectInvitation(session, roomId);
- if (mOnRoomChangedListener != null) {
- mOnRoomChangedListener.onRoomLeft(roomId);
- }
+ mActivity.onRejectInvitation(roomId, new SimpleApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ if (mOnRoomChangedListener != null) {
+ mOnRoomChangedListener.onRoomLeft(roomId);
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onForgetRoom(final MXSession session, final String roomId) {
+ mActivity.onForgetRoom(roomId, new SimpleApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ if (mOnRoomChangedListener != null) {
+ mOnRoomChangedListener.onRoomForgot(roomId);
}
}
});
}
+ @Override
+ public void addHomeScreenShortcut(MXSession session, String roomId) {
+ RoomUtils.addHomeScreenShortcut(getActivity(), session, roomId);
+ }
+
/*
* *********************************************************************************************
* Public methods
@@ -330,6 +354,15 @@ void openRoom(final Room room) {
}
}
+ /**
+ * Manage the fab actions
+ *
+ * @return true if the fragment has a dedicated action.
+ */
+ public boolean onFabClick() {
+ return false;
+ }
+
/*
* *********************************************************************************************
* Private methods
@@ -461,5 +494,7 @@ public interface OnRoomChangedListener {
void onToggleDirectChat(final String roomId, final boolean isDirectChat);
void onRoomLeft(final String roomId);
+
+ void onRoomForgot(final String roomId);
}
}
diff --git a/vector/src/main/java/im/vector/fragments/GroupDetailsBaseFragment.java b/vector/src/main/java/im/vector/fragments/GroupDetailsBaseFragment.java
new file mode 100755
index 0000000000..4d98f7af2e
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/GroupDetailsBaseFragment.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.fragments;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import org.matrix.androidsdk.MXSession;
+
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+import im.vector.Matrix;
+import im.vector.activity.VectorGroupDetailsActivity;
+
+public abstract class GroupDetailsBaseFragment extends Fragment {
+ private static final String LOG_TAG = GroupDetailsBaseFragment.class.getSimpleName();
+
+ private static final String CURRENT_FILTER = "CURRENT_FILTER";
+
+ protected MXSession mSession;
+ protected VectorGroupDetailsActivity mActivity;
+
+ // Butterknife unbinder
+ private Unbinder mUnBinder;
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mSession = Matrix.getInstance(getContext()).getDefaultSession();
+ mActivity = (VectorGroupDetailsActivity) getActivity();
+
+ initViews();
+ }
+
+ @Override
+ @CallSuper
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mUnBinder = ButterKnife.bind(this, view);
+ }
+
+ @Override
+ @CallSuper
+ public void onDestroyView() {
+ super.onDestroyView();
+ mUnBinder.unbind();
+ }
+
+ @Override
+ @CallSuper
+ public void onDetach() {
+ super.onDetach();
+ mActivity = null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (null != mActivity) {
+ // dismiss the keyboard when swiping
+ final View view = mActivity.getCurrentFocus();
+ if (view != null) {
+ final InputMethodManager inputMethodManager = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+ }
+ /*
+ * *********************************************************************************************
+ * Abstract methods
+ * *********************************************************************************************
+ */
+
+ protected abstract void initViews();
+ public abstract void refreshViews();
+}
diff --git a/vector/src/main/java/im/vector/fragments/GroupDetailsHomeFragment.java b/vector/src/main/java/im/vector/fragments/GroupDetailsHomeFragment.java
new file mode 100755
index 0000000000..3d52c421cb
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/GroupDetailsHomeFragment.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.fragments;
+
+import android.os.Bundle;
+
+import android.support.v4.content.ContextCompat;
+
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.matrix.androidsdk.rest.model.group.Group;
+
+import butterknife.BindView;
+import im.vector.R;
+import im.vector.activity.CommonActivityUtils;
+import im.vector.util.VectorImageGetter;
+import im.vector.util.VectorUtils;
+
+public class GroupDetailsHomeFragment extends GroupDetailsBaseFragment {
+ private static final String LOG_TAG = GroupDetailsHomeFragment.class.getSimpleName();
+
+ @BindView(R.id.group_avatar)
+ ImageView mGroupAvatar;
+
+ @BindView(R.id.group_name_text_view)
+ TextView mGroupNameTextView;
+
+ @BindView(R.id.group_topic_text_view)
+ TextView mGroupTopicTextView;
+
+ @BindView(R.id.group_members_icon_view)
+ ImageView mGroupMembersIconView;
+
+ @BindView(R.id.group_members_text_view)
+ TextView mGroupMembersTextView;
+
+ @BindView(R.id.group_rooms_icon_view)
+ ImageView mGroupRoomsIconView;
+
+ @BindView(R.id.group_rooms_text_view)
+ TextView mGroupRoomsTextView;
+
+ @BindView(R.id.html_text_view)
+ TextView mGroupHtmlTextView;
+
+ @BindView(R.id.no_html_text_view)
+ TextView noLongDescriptionTextView;
+
+ private VectorImageGetter mImageGetter;
+
+ /*
+ * *********************************************************************************************
+ * Fragment lifecycle
+ * *********************************************************************************************
+ */
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ if (null == mImageGetter) {
+ mImageGetter = new VectorImageGetter(mSession);
+ }
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_group_details_home, container, false);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mImageGetter.setListener(null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshViews();
+
+ mImageGetter.setListener(new VectorImageGetter.OnImageDownloadListener() {
+ @Override
+ public void onImageDownloaded(String source) {
+ // invalidate the text
+ refreshLongDescription();
+ }
+ });
+ }
+
+ /*
+ * *********************************************************************************************
+ * UI management
+ * *********************************************************************************************
+ */
+ @Override
+ protected void initViews() {
+ mGroupMembersIconView.setImageDrawable(CommonActivityUtils.tintDrawableWithColor(ContextCompat.getDrawable(mActivity, R.drawable.riot_tab_groups), mGroupMembersTextView.getCurrentTextColor()));
+ mGroupRoomsIconView.setImageDrawable(CommonActivityUtils.tintDrawableWithColor(ContextCompat.getDrawable(mActivity, R.drawable.riot_tab_rooms), mGroupMembersTextView.getCurrentTextColor()));
+ }
+
+ /*
+ * *********************************************************************************************
+ * Data management
+ * *********************************************************************************************
+ */
+
+ @Override
+ public void refreshViews() {
+ Group group = mActivity.getGroup();
+
+ VectorUtils.loadGroupAvatar(mActivity, mSession, mGroupAvatar, group);
+
+ mGroupNameTextView.setText(group.getDisplayName());
+
+ mGroupTopicTextView.setText(group.getShortDescription());
+ mGroupTopicTextView.setVisibility(TextUtils.isEmpty(mGroupTopicTextView.getText()) ? View.GONE : View.VISIBLE);
+
+ int roomCount = (null != group.getGroupRooms()) ? group.getGroupRooms().getEstimatedRoomCount() : 0;
+ int memberCount = (null != group.getGroupUsers()) ? group.getGroupUsers().getEstimatedUsersCount() : 1;
+
+ mGroupRoomsTextView.setText((1 == roomCount) ? getString(R.string.group_one_room) : getString(R.string.group_rooms, roomCount));
+ mGroupMembersTextView.setText((1 == memberCount) ? getString(R.string.group_one_member) : getString(R.string.group_members, memberCount));
+
+ if (!TextUtils.isEmpty(group.getLongDescription())) {
+ mGroupHtmlTextView.setVisibility(View.VISIBLE);
+ refreshLongDescription();
+ noLongDescriptionTextView.setVisibility(View.GONE);
+ } else {
+ noLongDescriptionTextView.setVisibility(View.VISIBLE);
+ mGroupHtmlTextView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Update the long description text
+ */
+ private void refreshLongDescription() {
+ if (null != mGroupHtmlTextView) {
+ Group group = mActivity.getGroup();
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ mGroupHtmlTextView.setText(Html.fromHtml(group.getLongDescription(), Html.FROM_HTML_MODE_LEGACY, mImageGetter, null));
+ } else {
+ mGroupHtmlTextView.setText(Html.fromHtml(group.getLongDescription(), mImageGetter, null));
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/fragments/GroupDetailsPeopleFragment.java b/vector/src/main/java/im/vector/fragments/GroupDetailsPeopleFragment.java
new file mode 100755
index 0000000000..fecfed1ecb
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/GroupDetailsPeopleFragment.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 OpenMarket Ltd
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.fragments;
+
+import android.os.Bundle;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+
+import org.matrix.androidsdk.rest.model.group.GroupUser;
+
+import butterknife.BindView;
+import im.vector.R;
+
+import im.vector.adapters.GroupDetailsPeopleAdapter;
+import im.vector.util.GroupUtils;
+import im.vector.view.EmptyViewItemDecoration;
+import im.vector.view.SimpleDividerItemDecoration;
+
+public class GroupDetailsPeopleFragment extends GroupDetailsBaseFragment {
+ @BindView(R.id.recyclerview)
+ RecyclerView mRecycler;
+
+ @BindView(R.id.search_view)
+ SearchView mSearchView;
+
+ private GroupDetailsPeopleAdapter mAdapter;
+ private String mCurrentFilter;
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_group_details_people, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshViews();
+ }
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mCurrentFilter = mSearchView.getQuery().toString();
+ mAdapter.onFilterDone(mCurrentFilter);
+ }
+ /*
+ * *********************************************************************************************
+ * UI management
+ * *********************************************************************************************
+ */
+
+ /**
+ * Prepare views
+ */
+ @Override
+ protected void initViews() {
+ int margin = (int) getResources().getDimension(R.dimen.item_decoration_left_margin);
+ mRecycler.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
+ mRecycler.addItemDecoration(new SimpleDividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL, margin));
+ mRecycler.addItemDecoration(new EmptyViewItemDecoration(getActivity(), DividerItemDecoration.VERTICAL, 40, 16, 14));
+ mAdapter = new GroupDetailsPeopleAdapter(getActivity(), new GroupDetailsPeopleAdapter.OnSelectUserListener() {
+ @Override
+ public void onSelectItem(GroupUser user, int position) {
+ GroupUtils.openGroupUserPage(mActivity, mSession, user);
+ }
+ });
+ mRecycler.setAdapter(mAdapter);
+
+ mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(final String newText) {
+ if (!TextUtils.equals(mCurrentFilter, newText)) {
+ mAdapter.getFilter().filter(newText, new Filter.FilterListener() {
+ @Override
+ public void onFilterComplete(int count) {
+ mCurrentFilter = newText;
+ }
+ });
+ }
+ return true;
+ }
+ });
+
+ mSearchView.setMaxWidth(Integer.MAX_VALUE);
+ mSearchView.setQueryHint(getString(R.string.filter_group_members));
+ mSearchView.setFocusable(false);
+ mSearchView.setIconifiedByDefault(false);
+ mSearchView.clearFocus();
+ }
+
+ @Override
+ public void refreshViews() {
+ mAdapter.setJoinedGroupUsers(mActivity.getGroup().getGroupUsers().getUsers());
+ mAdapter.setInvitedGroupUsers(mActivity.getGroup().getInvitedGroupUsers().getUsers());
+ }
+}
diff --git a/vector/src/main/java/im/vector/fragments/GroupDetailsRoomsFragment.java b/vector/src/main/java/im/vector/fragments/GroupDetailsRoomsFragment.java
new file mode 100755
index 0000000000..1e032843f8
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/GroupDetailsRoomsFragment.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 OpenMarket Ltd
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.fragments;
+
+import android.os.Bundle;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Filter;
+
+import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
+import org.matrix.androidsdk.rest.model.group.GroupRoom;
+
+import butterknife.BindView;
+import im.vector.R;
+import im.vector.adapters.GroupDetailsRoomsAdapter;
+import im.vector.util.GroupUtils;
+import im.vector.view.EmptyViewItemDecoration;
+import im.vector.view.SimpleDividerItemDecoration;
+
+public class GroupDetailsRoomsFragment extends GroupDetailsBaseFragment {
+ @BindView(R.id.recyclerview)
+ RecyclerView mRecycler;
+
+ @BindView(R.id.search_view)
+ SearchView mSearchView;
+
+ private GroupDetailsRoomsAdapter mAdapter;
+ private String mCurrentFilter;
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_group_details_rooms, container, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshViews();
+ }
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mCurrentFilter = mSearchView.getQuery().toString();
+ mAdapter.onFilterDone(mCurrentFilter);
+ }
+
+ /*
+ * *********************************************************************************************
+ * UI management
+ * *********************************************************************************************
+ */
+
+ /**
+ * Prepare views
+ */
+ @Override
+ protected void initViews() {
+ int margin = (int) getResources().getDimension(R.dimen.item_decoration_left_margin);
+ mRecycler.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
+ mRecycler.addItemDecoration(new SimpleDividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL, margin));
+ mRecycler.addItemDecoration(new EmptyViewItemDecoration(getActivity(), DividerItemDecoration.VERTICAL, 40, 16, 14));
+ mAdapter = new GroupDetailsRoomsAdapter(getActivity(), new GroupDetailsRoomsAdapter.OnSelectRoomListener() {
+ @Override
+ public void onSelectItem(GroupRoom groupRoom, int position) {
+ mActivity.showWaitingView();
+ GroupUtils.openGroupRoom(mActivity, mSession, groupRoom, new SimpleApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ mActivity.stopWaitingView();
+ }
+ });
+ }
+ });
+ mRecycler.setAdapter(mAdapter);
+
+ mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(final String newText) {
+ if (!TextUtils.equals(mCurrentFilter, newText)) {
+ mAdapter.getFilter().filter(newText, new Filter.FilterListener() {
+ @Override
+ public void onFilterComplete(int count) {
+ mCurrentFilter = newText;
+ }
+ });
+ }
+ return true;
+ }
+ });
+ mSearchView.setMaxWidth(Integer.MAX_VALUE);
+ mSearchView.setQueryHint(getString(R.string.filter_group_rooms));
+ mSearchView.setFocusable(false);
+ mSearchView.setIconifiedByDefault(false);
+ mSearchView.clearFocus();
+ }
+
+ @Override
+ public void refreshViews() {
+ mAdapter.setGroupRooms(mActivity.getGroup().getGroupRooms().getRoomsList());
+ }
+}
diff --git a/vector/src/main/java/im/vector/fragments/GroupsFragment.java b/vector/src/main/java/im/vector/fragments/GroupsFragment.java
new file mode 100644
index 0000000000..7db8db0e07
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/GroupsFragment.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.fragments;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Filter;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.matrix.androidsdk.MXSession;
+import org.matrix.androidsdk.data.Room;
+import org.matrix.androidsdk.groups.GroupsManager;
+import org.matrix.androidsdk.listeners.MXEventListener;
+import org.matrix.androidsdk.rest.callback.ApiCallback;
+import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
+import org.matrix.androidsdk.rest.model.MatrixError;
+import org.matrix.androidsdk.rest.model.group.Group;
+import org.matrix.androidsdk.util.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import java.util.List;
+
+import butterknife.BindView;
+import im.vector.R;
+import im.vector.activity.CommonActivityUtils;
+import im.vector.activity.VectorGroupDetailsActivity;
+import im.vector.adapters.AbsAdapter;
+import im.vector.adapters.GroupAdapter;
+import im.vector.util.ThemeUtils;
+import im.vector.util.VectorUtils;
+import im.vector.view.EmptyViewItemDecoration;
+import im.vector.view.SimpleDividerItemDecoration;
+
+public class GroupsFragment extends AbsHomeFragment {
+ private static final String LOG_TAG = GroupsFragment.class.getSimpleName();
+
+ @BindView(R.id.recyclerview)
+ RecyclerView mRecycler;
+
+ // groups management
+ private GroupAdapter mAdapter;
+ private GroupsManager mGroupsManager;
+
+ // rooms list
+ private final List mJoinedGroups = new ArrayList<>();
+ private final List mInvitedGroups = new ArrayList<>();
+
+ // refresh when there is a group event
+ private final MXEventListener mEventListener = new MXEventListener() {
+ @Override
+ public void onNewGroupInvitation(String groupId) {
+ refreshGroups();
+ }
+
+ @Override
+ public void onJoinGroup(String groupId) {
+ refreshGroups();
+ }
+
+ @Override
+ public void onLeaveGroup(String groupId) {
+ refreshGroups();
+ }
+ };
+
+ /*
+ * *********************************************************************************************
+ * Static methods
+ * *********************************************************************************************
+ */
+
+ public static GroupsFragment newInstance() {
+ return new GroupsFragment();
+ }
+
+ /*
+ * *********************************************************************************************
+ * Fragment lifecycle
+ * *********************************************************************************************
+ */
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_groups, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mGroupsManager = mSession.getGroupsManager();
+ mPrimaryColor = ContextCompat.getColor(getActivity(), R.color.tab_groups);
+ mSecondaryColor = ContextCompat.getColor(getActivity(), R.color.tab_groups_secondary);
+
+ initViews();
+
+ mAdapter.onFilterDone(mCurrentFilter);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mSession.getDataHandler().addListener(mEventListener);
+ mRecycler.addOnScrollListener(mScrollListener);
+ refreshGroupsAndProfiles();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mSession.getDataHandler().removeListener(mEventListener);
+ mRecycler.removeOnScrollListener(mScrollListener);
+ }
+
+ /*
+ * *********************************************************************************************
+ * Abstract methods implementation
+ * *********************************************************************************************
+ */
+
+ @Override
+ protected List getRooms() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ protected void onFilter(String pattern, final OnFilterListener listener) {
+ mAdapter.getFilter().filter(pattern, new Filter.FilterListener() {
+ @Override
+ public void onFilterComplete(int count) {
+ Log.i(LOG_TAG, "onFilterComplete " + count);
+ if (listener != null) {
+ listener.onFilterDone(count);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onResetFilter() {
+ mAdapter.getFilter().filter("", new Filter.FilterListener() {
+ @Override
+ public void onFilterComplete(int count) {
+ Log.i(LOG_TAG, "onResetFilter " + count);
+ }
+ });
+ }
+
+ /*
+ * *********************************************************************************************
+ * UI management
+ * *********************************************************************************************
+ */
+
+ private void initViews() {
+ int margin = (int) getResources().getDimension(R.dimen.item_decoration_left_margin);
+ mRecycler.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
+ mRecycler.addItemDecoration(new SimpleDividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL, margin));
+ mRecycler.addItemDecoration(new EmptyViewItemDecoration(getActivity(), DividerItemDecoration.VERTICAL, 40, 16, 14));
+
+ mAdapter = new GroupAdapter(getActivity(), new GroupAdapter.OnGroupSelectItemListener() {
+ @Override
+ public void onSelectItem(final Group group, final int position) {
+ // display it
+ Intent intent = new Intent(getActivity(), VectorGroupDetailsActivity.class);
+ intent.putExtra(VectorGroupDetailsActivity.EXTRA_GROUP_ID, group.getGroupId());
+ intent.putExtra(VectorGroupDetailsActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
+ startActivity(intent);
+ }
+
+ @Override
+ public boolean onLongPressItem(Group item, int position) {
+ VectorUtils.copyToClipboard(getActivity(), item.getGroupId());
+ return true;
+ }
+ }, new AbsAdapter.GroupInvitationListener() {
+ @Override
+ public void onJoinGroup(MXSession session, String groupId) {
+ mActivity.showWaitingView();
+ mGroupsManager.joinGroup(groupId, new ApiCallback() {
+
+ private void onDone(String errorMessage) {
+ if ((null != errorMessage) && (null != getActivity())) {
+ Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
+ }
+ mActivity.stopWaitingView();
+ }
+
+ @Override
+ public void onSuccess(Void info) {
+ onDone(null);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onDone(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onDone(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onDone(e.getLocalizedMessage());
+ }
+ });
+ }
+
+ @Override
+ public void onRejectInvitation(MXSession session, String groupId) {
+ leaveOrReject(groupId);
+ }
+ }, new AbsAdapter.MoreGroupActionListener() {
+ @Override
+ public void onMoreActionClick(View itemView, Group group) {
+ displayGroupPopupMenu(group, itemView);
+ }
+ });
+
+ mRecycler.setAdapter(mAdapter);
+ }
+
+ /**
+ * Refresh the groups list
+ */
+ private void refreshGroups() {
+ mJoinedGroups.clear();
+ mJoinedGroups.addAll(mGroupsManager.getJoinedGroups());
+ mAdapter.setGroups(mJoinedGroups);
+
+ mInvitedGroups.clear();
+ mInvitedGroups.addAll(mGroupsManager.getInvitedGroups());
+ mAdapter.setInvitedGroups(mInvitedGroups);
+ }
+
+ /**
+ * refresh the groups list and their profiles.
+ */
+ private void refreshGroupsAndProfiles() {
+ refreshGroups();
+ mSession.getGroupsManager().refreshGroupProfiles(new SimpleApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ if ((null != mActivity) && !mActivity.isFinishing()) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ });
+ }
+
+ /**
+ * Leave or reject a group invitation.
+ *
+ * @param groupId the group Id
+ */
+ private void leaveOrReject(String groupId) {
+ mActivity.showWaitingView();
+ mGroupsManager.leaveGroup(groupId, new ApiCallback() {
+
+ private void onDone(String errorMessage) {
+ if ((null != errorMessage) && (null != getActivity())) {
+ Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
+ }
+ mActivity.stopWaitingView();
+ }
+
+ @Override
+ public void onSuccess(Void info) {
+ onDone(null);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onDone(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onDone(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onDone(e.getLocalizedMessage());
+ }
+ });
+ }
+
+ @SuppressLint("NewApi")
+ private void displayGroupPopupMenu(final Group group, final View actionView) {
+ final Context context = getActivity();
+ final PopupMenu popup;
+
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ popup = new PopupMenu(context, actionView, Gravity.END);
+ } else {
+ popup = new PopupMenu(context, actionView);
+ }
+ popup.getMenuInflater().inflate(R.menu.vector_home_group_settings, popup.getMenu());
+ CommonActivityUtils.tintMenuIcons(popup.getMenu(), ThemeUtils.getColor(context, R.attr.settings_icon_tint_color));
+
+
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.ic_action_select_remove_group: {
+ leaveOrReject(group.getGroupId());
+ break;
+ }
+ }
+ return false;
+ }
+ });
+
+
+ // force to display the icon
+ try {
+ Field[] fields = popup.getClass().getDeclaredFields();
+ for (Field field : fields) {
+ if ("mPopup".equals(field.getName())) {
+ field.setAccessible(true);
+ Object menuPopupHelper = field.get(popup);
+ Class> classPopupHelper = Class.forName(menuPopupHelper.getClass().getName());
+ Method setForceIcons = classPopupHelper.getMethod("setForceShowIcon", boolean.class);
+ setForceIcons.invoke(menuPopupHelper, true);
+ break;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## displayGroupPopupMenu() : failed " + e.getMessage());
+ }
+
+ popup.show();
+ }
+
+ @Override
+ public boolean onFabClick() {
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
+ View dialogView = getActivity().getLayoutInflater().inflate(R.layout.dialog_create_group, null);
+ alertDialogBuilder.setView(dialogView);
+
+ final EditText nameEditText = dialogView.findViewById(R.id.community_name_edit_text);
+ final EditText idEditText = dialogView.findViewById(R.id.community_id_edit_text);
+ final String hostName = mSession.getHomeServerConfig().getHomeserverUri().getHost();
+ TextView hsNameView = dialogView.findViewById(R.id.community_hs_name_text_view);
+ hsNameView.setText(":" + hostName);
+
+ // set dialog message
+ alertDialogBuilder
+ .setCancelable(false)
+ .setTitle(R.string.create_community)
+ .setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ String localPart = idEditText.getText().toString().trim();
+ String groupName = nameEditText.getText().toString().trim();
+
+ mActivity.showWaitingView();
+
+ mGroupsManager.createGroup(localPart, groupName, new ApiCallback() {
+ private void onDone(String errorMessage) {
+ if (null != getActivity()) {
+ if (null != errorMessage) {
+ Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show();
+ }
+
+ mActivity.stopWaitingView();
+
+ refreshGroups();
+ }
+ }
+
+ @Override
+ public void onSuccess(String groupId) {
+ onDone(null);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onDone(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onDone(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onDone(e.getLocalizedMessage());
+ }
+ });
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+
+ // create alert dialog
+ AlertDialog alertDialog = alertDialogBuilder.create();
+
+ // show it
+ alertDialog.show();
+
+ final Button createButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ idEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ createButton.setEnabled(MXSession.isGroupId("+" + idEditText.getText().toString().trim() + ":" + hostName));
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ });
+
+ createButton.setEnabled(false);
+ return true;
+ }
+}
diff --git a/vector/src/main/java/im/vector/fragments/HomeFragment.java b/vector/src/main/java/im/vector/fragments/HomeFragment.java
index 13ab98b08f..b3e0bfd9f3 100644
--- a/vector/src/main/java/im/vector/fragments/HomeFragment.java
+++ b/vector/src/main/java/im/vector/fragments/HomeFragment.java
@@ -51,7 +51,7 @@
import im.vector.util.RoomUtils;
import im.vector.view.HomeSectionView;
-public class HomeFragment extends AbsHomeFragment implements HomeRoomAdapter.OnSelectRoomListener {
+public class HomeFragment extends AbsHomeFragment implements HomeRoomAdapter.OnSelectRoomListener, AbsHomeFragment.OnRoomChangedListener {
private static final String LOG_TAG = HomeFragment.class.getSimpleName();
@BindView(R.id.nested_scrollview)
@@ -108,6 +108,8 @@ public void onActivityCreated(final Bundle savedInstanceState) {
initViews();
+ mOnRoomChangedListener = this;
+
// Eventually restore the pattern of adapter after orientation change
for (HomeSectionView homeSectionView : mHomeSectionViews) {
homeSectionView.setCurrentFilter(mCurrentFilter);
@@ -171,33 +173,33 @@ private void initViews() {
mInvitationsSection.setTitle(R.string.invitations_header);
mInvitationsSection.setHideIfEmpty(true);
mInvitationsSection.setPlaceholders(null, getString(R.string.no_result_placeholder));
- mInvitationsSection.setupRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false),
+ mInvitationsSection.setupRoomRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false),
R.layout.adapter_item_room_invite, false, this, this, null);
// Favourites
mFavouritesSection.setTitle(R.string.bottom_action_favourites);
mFavouritesSection.setHideIfEmpty(true);
mFavouritesSection.setPlaceholders(null, getString(R.string.no_result_placeholder));
- mFavouritesSection.setupRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
+ mFavouritesSection.setupRoomRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
R.layout.adapter_item_circular_room_view, true, this, null, null);
// People
mDirectChatsSection.setTitle(R.string.bottom_action_people);
mDirectChatsSection.setPlaceholders(getString(R.string.no_conversation_placeholder), getString(R.string.no_result_placeholder));
- mDirectChatsSection.setupRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
+ mDirectChatsSection.setupRoomRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
R.layout.adapter_item_circular_room_view, true, this, null, null);
// Rooms
mRoomsSection.setTitle(R.string.bottom_action_rooms);
mRoomsSection.setPlaceholders(getString(R.string.no_room_placeholder), getString(R.string.no_result_placeholder));
- mRoomsSection.setupRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
+ mRoomsSection.setupRoomRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
R.layout.adapter_item_circular_room_view, true, this, null, null);
// Low priority
mLowPrioritySection.setTitle(R.string.low_priority_header);
mLowPrioritySection.setHideIfEmpty(true);
mLowPrioritySection.setPlaceholders(null, getString(R.string.no_result_placeholder));
- mLowPrioritySection.setupRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
+ mLowPrioritySection.setupRoomRecyclerView(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false),
R.layout.adapter_item_circular_room_view, true, this, null, null);
mHomeSectionViews = Arrays.asList(mInvitationsSection, mFavouritesSection, mDirectChatsSection, mRoomsSection, mLowPrioritySection);
@@ -352,4 +354,24 @@ public void onLongClickRoom(View v, Room room, int position) {
RoomUtils.displayPopupMenu(getActivity(), mSession, room, v, isFavorite, isLowPriority, this);
}
+
+ /*
+ * *********************************************************************************************
+ * Listeners
+ * *********************************************************************************************
+ */
+
+ @Override
+ public void onToggleDirectChat(String roomId, boolean isDirectChat) {
+ }
+
+ @Override
+ public void onRoomLeft(String roomId) {
+ }
+
+ @Override
+ public void onRoomForgot(String roomId) {
+ // there is no sync event when a room is forgotten
+ initData();
+ }
}
diff --git a/vector/src/main/java/im/vector/fragments/PeopleFragment.java b/vector/src/main/java/im/vector/fragments/PeopleFragment.java
index a7a8850b31..6aeb732366 100644
--- a/vector/src/main/java/im/vector/fragments/PeopleFragment.java
+++ b/vector/src/main/java/im/vector/fragments/PeopleFragment.java
@@ -44,7 +44,7 @@
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.Search.SearchUsersResponse;
+import org.matrix.androidsdk.rest.model.search.SearchUsersResponse;
import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.util.Log;
@@ -68,7 +68,6 @@
import im.vector.view.SimpleDividerItemDecoration;
public class PeopleFragment extends AbsHomeFragment implements ContactsManager.ContactsManagerListener, AbsHomeFragment.OnRoomChangedListener {
-
private static final String LOG_TAG = PeopleFragment.class.getSimpleName();
private static final String MATRIX_USER_ONLY_PREF_KEY = "MATRIX_USER_ONLY_PREF_KEY";
@@ -610,4 +609,9 @@ public void onToggleDirectChat(String roomId, boolean isDirectChat) {
public void onRoomLeft(String roomId) {
mAdapter.removeDirectChat(roomId);
}
+
+ @Override
+ public void onRoomForgot(String roomId) {
+ mAdapter.removeDirectChat(roomId);
+ }
}
diff --git a/vector/src/main/java/im/vector/fragments/RoomsFragment.java b/vector/src/main/java/im/vector/fragments/RoomsFragment.java
index 5696f8e759..a9a070c3bf 100644
--- a/vector/src/main/java/im/vector/fragments/RoomsFragment.java
+++ b/vector/src/main/java/im/vector/fragments/RoomsFragment.java
@@ -41,7 +41,7 @@
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.client.EventsRestClient;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.PublicRoom;
+import org.matrix.androidsdk.rest.model.publicroom.PublicRoom;
import org.matrix.androidsdk.util.Log;
import java.util.ArrayList;
@@ -63,7 +63,7 @@
import im.vector.view.SimpleDividerItemDecoration;
public class RoomsFragment extends AbsHomeFragment implements AbsHomeFragment.OnRoomChangedListener {
- private static final String LOG_TAG = PeopleFragment.class.getSimpleName();
+ private static final String LOG_TAG = RoomsFragment.class.getSimpleName();
// activity result codes
private static final int DIRECTORY_SOURCE_ACTIVITY_REQUEST_CODE = 314;
@@ -669,11 +669,15 @@ private void removePublicRoomsListener() {
@Override
public void onToggleDirectChat(String roomId, boolean isDirectChat) {
-
}
@Override
public void onRoomLeft(String roomId) {
+ }
+ @Override
+ public void onRoomForgot(String roomId) {
+ // there is no sync event when a room is forgotten
+ refreshRooms();
}
}
diff --git a/vector/src/main/java/im/vector/fragments/VectorMessageListFragment.java b/vector/src/main/java/im/vector/fragments/VectorMessageListFragment.java
index fd0f4e3f18..d04b0193d0 100755
--- a/vector/src/main/java/im/vector/fragments/VectorMessageListFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorMessageListFragment.java
@@ -54,23 +54,22 @@
import org.matrix.androidsdk.listeners.MXMediaDownloadListener;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
-import org.matrix.androidsdk.rest.model.EncryptedEventContent;
-import org.matrix.androidsdk.rest.model.EncryptedFileInfo;
+import org.matrix.androidsdk.rest.model.crypto.EncryptedEventContent;
+import org.matrix.androidsdk.rest.model.crypto.EncryptedFileInfo;
import org.matrix.androidsdk.rest.model.Event;
-import org.matrix.androidsdk.rest.model.FileMessage;
-import org.matrix.androidsdk.rest.model.ImageMessage;
+import org.matrix.androidsdk.rest.model.message.FileMessage;
+import org.matrix.androidsdk.rest.model.message.ImageMessage;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.Message;
-import org.matrix.androidsdk.rest.model.VideoMessage;
+import org.matrix.androidsdk.rest.model.message.Message;
+import org.matrix.androidsdk.rest.model.message.VideoMessage;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.androidsdk.util.Log;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import im.vector.Matrix;
import im.vector.R;
@@ -84,8 +83,10 @@
import im.vector.db.VectorContentProvider;
import im.vector.listeners.IMessagesAdapterActionsListener;
import im.vector.receiver.VectorUniversalLinkReceiver;
+import im.vector.util.PreferencesManager;
import im.vector.util.SlidableMediaInfo;
import im.vector.util.ThemeUtils;
+import im.vector.util.VectorImageGetter;
import im.vector.util.VectorUtils;
import im.vector.widgets.WidgetsManager;
@@ -97,6 +98,8 @@ public interface IListFragmentEventListener {
}
private static final String TAG_FRAGMENT_RECEIPTS_DIALOG = "TAG_FRAGMENT_RECEIPTS_DIALOG";
+ private static final String TAG_FRAGMENT_USER_GROUPS_DIALOG = "TAG_FRAGMENT_USER_GROUPS_DIALOG";
+
private IListFragmentEventListener mHostActivityListener;
// onMediaAction actions
@@ -110,6 +113,8 @@ public interface IListFragmentEventListener {
private View mForwardProgressView;
private View mMainProgressView;
+ private VectorImageGetter mVectorImageGetter;
+
public static VectorMessageListFragment newInstance(String matrixId, String roomId, String eventId, String previewMode, int layoutResId) {
VectorMessageListFragment f = new VectorMessageListFragment();
Bundle args = new Bundle();
@@ -146,6 +151,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
((VectorMessagesAdapter) mAdapter).mIsRoomEncrypted = mRoom.isEncrypted();
}
+ if (null != mSession) {
+ mVectorImageGetter = new VectorImageGetter(mSession);
+ ((VectorMessagesAdapter) mAdapter).setImageGetter(mVectorImageGetter);
+ }
+
mMessageListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
@@ -202,6 +212,8 @@ public void onPause() {
adapter.setVectorMessagesAdapterActionsListener(null);
adapter.onPause();
}
+
+ mVectorImageGetter.setListener(null);
}
@@ -212,6 +224,13 @@ public void onResume() {
VectorMessagesAdapter adapter = ((VectorMessagesAdapter) mAdapter);
adapter.setVectorMessagesAdapterActionsListener(this);
}
+
+ mVectorImageGetter.setListener(new VectorImageGetter.OnImageDownloadListener() {
+ @Override
+ public void onImageDownloaded(String source) {
+ mAdapter.notifyDataSetChanged();
+ }
+ });
}
/**
@@ -757,64 +776,65 @@ public void onClick(DialogInterface dialog, int which) {
*/
void onMediaAction(final int menuAction, final String mediaUrl, final String mediaMimeType, final String filename, final EncryptedFileInfo encryptedFileInfo) {
MXMediasCache mediasCache = Matrix.getInstance(getActivity()).getMediasCache();
- File file = mediasCache.mediaCacheFile(mediaUrl, mediaMimeType);
-
// check if the media has already been downloaded
- if (null != file) {
- // download
- if ((menuAction == ACTION_VECTOR_SAVE) || (menuAction == ACTION_VECTOR_OPEN)) {
- CommonActivityUtils.saveMediaIntoDownloads(getActivity(), file, filename, mediaMimeType, new SimpleApiCallback() {
- @Override
- public void onSuccess(String savedMediaPath) {
- if (null != savedMediaPath) {
- if (menuAction == ACTION_VECTOR_SAVE) {
- Toast.makeText(getActivity(), getText(R.string.media_slider_saved), Toast.LENGTH_LONG).show();
- } else {
- CommonActivityUtils.openMedia(getActivity(), savedMediaPath, mediaMimeType);
- }
- }
+ if (mediasCache.isMediaCached(mediaUrl, mediaMimeType)) {
+ mediasCache.createTmpMediaFile(mediaUrl, mediaMimeType, encryptedFileInfo, new SimpleApiCallback() {
+ @Override
+ public void onSuccess(File file) {
+ // sanity check
+ if (null == file) {
+ return;
}
- });
- } else {
- // shared / forward
- Uri mediaUri = null;
- File renamedFile = file;
+ if ((menuAction == ACTION_VECTOR_SAVE) || (menuAction == ACTION_VECTOR_OPEN)) {
+ CommonActivityUtils.saveMediaIntoDownloads(getActivity(), file, filename, mediaMimeType, new SimpleApiCallback() {
+ @Override
+ public void onSuccess(String savedMediaPath) {
+ if (null != savedMediaPath) {
+ if (menuAction == ACTION_VECTOR_SAVE) {
+ Toast.makeText(getActivity(), getText(R.string.media_slider_saved), Toast.LENGTH_LONG).show();
+ } else {
+ CommonActivityUtils.openMedia(getActivity(), savedMediaPath, mediaMimeType);
+ }
+ }
+ }
+ });
+ } else {
+ if (null != filename) {
+ File dstFile = new File(file.getParent(), filename);
- if (!TextUtils.isEmpty(filename)) {
- try {
- InputStream fin = new FileInputStream(file);
- String tmpUrl = mediasCache.saveMedia(fin, filename, mediaMimeType);
+ if (dstFile.exists()) {
+ dstFile.delete();
+ }
- if (null != tmpUrl) {
- renamedFile = mediasCache.mediaCacheFile(tmpUrl, mediaMimeType);
+ file.renameTo(dstFile);
+ file = dstFile;
}
- } catch (Exception e) {
- Log.e(LOG_TAG, "onMediaAction shared / forward failed : " + e.getLocalizedMessage());
- }
- }
- if (null != renamedFile) {
- try {
- mediaUri = VectorContentProvider.absolutePathToUri(getActivity(), renamedFile.getAbsolutePath());
- } catch (Exception e) {
- Log.e(LOG_TAG, "onMediaAction VectorContentProvider.absolutePathToUri: " + e.getLocalizedMessage());
- }
- }
+ // shared / forward
+ Uri mediaUri = null;
+ try {
+ mediaUri = VectorContentProvider.absolutePathToUri(getActivity(), file.getAbsolutePath());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "onMediaAction VectorContentProvider.absolutePathToUri: " + e.getMessage());
+ }
- if (null != mediaUri) {
- final Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.setType(mediaMimeType);
- sendIntent.putExtra(Intent.EXTRA_STREAM, mediaUri);
- if (menuAction == ACTION_VECTOR_FORWARD) {
- CommonActivityUtils.sendFilesTo(getActivity(), sendIntent);
- } else {
- startActivity(sendIntent);
+ if (null != mediaUri) {
+ final Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.setType(mediaMimeType);
+ sendIntent.putExtra(Intent.EXTRA_STREAM, mediaUri);
+
+ if (menuAction == ACTION_VECTOR_FORWARD) {
+ CommonActivityUtils.sendFilesTo(getActivity(), sendIntent);
+ } else {
+ startActivity(sendIntent);
+ }
+ }
}
}
- }
+ });
} else {
// else download it
final String downloadId = mediasCache.downloadMedia(getActivity().getApplicationContext(), mSession.getHomeServerConfig(), mediaUrl, mediaMimeType, encryptedFileInfo);
@@ -854,8 +874,7 @@ public void run() {
*/
@Override
public boolean isDisplayAllEvents() {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
- return preferences.getBoolean(getString(R.string.settings_key_display_all_events), false);
+ return PreferencesManager.displayAllEvents(getActivity());
}
private void setViewVisibility(View view, int visibility) {
@@ -1101,6 +1120,22 @@ public void onMoreReadReceiptClick(String eventId) {
}
}
+ @Override
+ public void onGroupFlairClick(String userId, List groupIds) {
+ try {
+ FragmentManager fm = getActivity().getSupportFragmentManager();
+
+ VectorUserGroupsDialogFragment fragment = (VectorUserGroupsDialogFragment) fm.findFragmentByTag(TAG_FRAGMENT_USER_GROUPS_DIALOG);
+ if (fragment != null) {
+ fragment.dismissAllowingStateLoss();
+ }
+ fragment = VectorUserGroupsDialogFragment.newInstance(mSession.getMyUserId(), userId, groupIds);
+ fragment.show(fm, TAG_FRAGMENT_USER_GROUPS_DIALOG);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## onGroupFlairClick() failed " + e.getMessage());
+ }
+ }
+
@Override
public void onURLClick(Uri uri) {
try {
@@ -1173,6 +1208,15 @@ public void onMessageIdClick(String messageId) {
}
}
+ @Override
+ public void onGroupIdClick(String groupId) {
+ try {
+ onURLClick(Uri.parse(VectorUtils.getPermalink(groupId, null)));
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "onRoomIdClick failed " + e.getLocalizedMessage());
+ }
+ }
+
private int mInvalidIndexesCount = 0;
@Override
diff --git a/vector/src/main/java/im/vector/fragments/VectorPublicRoomsListFragment.java b/vector/src/main/java/im/vector/fragments/VectorPublicRoomsListFragment.java
index c6b9292fc7..2b811634ff 100755
--- a/vector/src/main/java/im/vector/fragments/VectorPublicRoomsListFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorPublicRoomsListFragment.java
@@ -36,7 +36,7 @@
import org.matrix.androidsdk.data.RoomPreviewData;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.PublicRoom;
+import org.matrix.androidsdk.rest.model.publicroom.PublicRoom;
import im.vector.Matrix;
import im.vector.PublicRoomsManager;
diff --git a/vector/src/main/java/im/vector/fragments/VectorReadReceiptsDialogFragment.java b/vector/src/main/java/im/vector/fragments/VectorReadReceiptsDialogFragment.java
index 1517f583f8..3274a881c9 100644
--- a/vector/src/main/java/im/vector/fragments/VectorReadReceiptsDialogFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorReadReceiptsDialogFragment.java
@@ -20,7 +20,6 @@
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.TextUtils;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -29,6 +28,7 @@
import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.db.MXMediasCache;
+import org.matrix.androidsdk.util.Log;
import java.util.ArrayList;
diff --git a/vector/src/main/java/im/vector/fragments/VectorRecentsListFragment.java b/vector/src/main/java/im/vector/fragments/VectorRecentsListFragment.java
index 8f9499abf8..cca156979b 100644
--- a/vector/src/main/java/im/vector/fragments/VectorRecentsListFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorRecentsListFragment.java
@@ -742,12 +742,69 @@ public void onClick(DialogInterface dialog, int which) {
}
@Override
- public void onToggleRoomNotifications(MXSession session, String roomId) {
+ public void onForgetRoom(final MXSession session, final String roomId) {
+ Room room = session.getDataHandler().getRoom(roomId);
+
+ if (null != room) {
+ showWaitingView();
+
+ room.forget(new ApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ if (null != getActivity()) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // clear any pending notification for this room
+ EventStreamService.cancelNotificationsForRoomId(mSession.getMyUserId(), roomId);
+ hideWaitingView();
+ }
+ });
+ }
+ }
+
+ private void onError(final String message) {
+ if (null != getActivity()) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ hideWaitingView();
+ Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onError(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onError(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onError(e.getLocalizedMessage());
+ }
+ });
+ }
+ }
+
+ @Override
+ public void addHomeScreenShortcut(MXSession session, String roomId) {
+ RoomUtils.addHomeScreenShortcut(getActivity(), session, roomId);
+ }
+
+ @Override
+ public void onUpdateRoomNotificationsState(MXSession session, String roomId, BingRulesManager.RoomNotificationState state) {
BingRulesManager bingRulesManager = session.getDataHandler().getBingRulesManager();
showWaitingView();
- bingRulesManager.muteRoomNotifications(roomId, !bingRulesManager.isRoomNotificationsDisabled(roomId), new BingRulesManager.onBingRuleUpdateListener() {
+ bingRulesManager.updateRoomNotificationState(roomId, state, new BingRulesManager.onBingRuleUpdateListener() {
@Override
public void onBingRuleUpdateSuccess() {
if (null != getActivity()) {
@@ -773,7 +830,6 @@ public void run() {
}
);
}
-
}
});
}
diff --git a/vector/src/main/java/im/vector/fragments/VectorRoomDetailsMembersFragment.java b/vector/src/main/java/im/vector/fragments/VectorRoomDetailsMembersFragment.java
index 731cd2eb1d..8feca35efe 100755
--- a/vector/src/main/java/im/vector/fragments/VectorRoomDetailsMembersFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorRoomDetailsMembersFragment.java
@@ -66,7 +66,6 @@
import java.util.TimerTask;
import im.vector.R;
-import im.vector.VectorApp;
import im.vector.activity.CommonActivityUtils;
import im.vector.activity.MXCActionBarActivity;
import im.vector.activity.VectorMemberDetailsActivity;
diff --git a/vector/src/main/java/im/vector/fragments/VectorRoomSettingsFragment.java b/vector/src/main/java/im/vector/fragments/VectorRoomSettingsFragment.java
index 58a381191d..e294f0ea76 100755
--- a/vector/src/main/java/im/vector/fragments/VectorRoomSettingsFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorRoomSettingsFragment.java
@@ -73,9 +73,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
import im.vector.Matrix;
-import im.vector.R;
+import im.vector.R;;
import im.vector.VectorApp;
import im.vector.activity.CommonActivityUtils;
import im.vector.activity.VectorMediasPickerActivity;
@@ -114,7 +115,7 @@ public class VectorRoomSettingsFragment extends PreferenceFragment implements Sh
private static final String PREF_KEY_ROOM_TAG_LIST = "roomTagList";
private static final String PREF_KEY_ROOM_ACCESS_RULES_LIST = "roomAccessRulesList";
private static final String PREF_KEY_ROOM_HISTORY_READABILITY_LIST = "roomReadHistoryRulesList";
- private static final String PREF_KEY_ROOM_MUTE_NOTIFICATIONS_SWITCH = "muteNotificationsSwitch";
+ private static final String PREF_KEY_ROOM_NOTIFICATIONS_LIST = "roomNotificationPreference";
private static final String PREF_KEY_ROOM_LEAVE = "roomLeave";
private static final String PREF_KEY_ROOM_INTERNAL_ID = "roomInternalId";
private static final String PREF_KEY_ADDRESSES = "addresses";
@@ -124,12 +125,17 @@ public class VectorRoomSettingsFragment extends PreferenceFragment implements Sh
private static final String PREF_KEY_BANNED_DIVIDER = "banned_divider";
private static final String PREF_KEY_ENCRYPTION = "encryptionKey";
+ private static final String PREF_KEY_FLAIR = "flair";
+ private static final String PREF_KEY_FLAIR_DIVIDER = "flair_divider";
+
private static final String ADDRESSES_PREFERENCE_KEY_BASE = "ADDRESSES_PREFERENCE_KEY_BASE";
private static final String NO_LOCAL_ADDRESS_PREFERENCE_KEY = "NO_LOCAL_ADDRESS_PREFERENCE_KEY";
private static final String ADD_ADDRESSES_PREFERENCE_KEY = "ADD_ADDRESSES_PREFERENCE_KEY";
private static final String BANNED_PREFERENCE_KEY_BASE = "BANNED_PREFERENCE_KEY_BASE";
+ private static final String FLAIR_PREFERENCE_KEY_BASE = "FLAIR_PREFERENCE_KEY_BASE";
+
private static final String UNKNOWN_VALUE = "UNKNOWN_VALUE";
// business code
@@ -148,17 +154,20 @@ public class VectorRoomSettingsFragment extends PreferenceFragment implements Sh
private PreferenceCategory mBannedMembersSettingsCategory;
private PreferenceCategory mBannedMembersSettingsCategoryDivider;
+ // flair
+ private PreferenceCategory mFlairSettingsCategory;
+
// UI elements
private RoomAvatarPreference mRoomPhotoAvatar;
private EditTextPreference mRoomNameEditTxt;
private EditTextPreference mRoomTopicEditTxt;
private CheckBoxPreference mRoomDirectoryVisibilitySwitch;
- private CheckBoxPreference mRoomMuteNotificationsSwitch;
private ListPreference mRoomTagListPreference;
private VectorListPreference mRoomAccessRulesListPreference;
private ListPreference mRoomHistoryReadabilityRulesListPreference;
private View mParentLoadingView;
private View mParentFragmentContainerView;
+ private ListPreference mRoomNotificationsPreference;
// disable some updates if there is
private final IMXNetworkEventListener mNetworkListener = new IMXNetworkEventListener() {
@@ -326,7 +335,6 @@ public void onCreate(Bundle savedInstanceState) {
mRoomNameEditTxt = (EditTextPreference) findPreference(PREF_KEY_ROOM_NAME);
mRoomTopicEditTxt = (EditTextPreference) findPreference(PREF_KEY_ROOM_TOPIC);
mRoomDirectoryVisibilitySwitch = (CheckBoxPreference) findPreference(PREF_KEY_ROOM_DIRECTORY_VISIBILITY_SWITCH);
- mRoomMuteNotificationsSwitch = (CheckBoxPreference) findPreference(PREF_KEY_ROOM_MUTE_NOTIFICATIONS_SWITCH);
mRoomTagListPreference = (ListPreference) findPreference(PREF_KEY_ROOM_TAG_LIST);
mRoomAccessRulesListPreference = (VectorListPreference) findPreference(PREF_KEY_ROOM_ACCESS_RULES_LIST);
mRoomHistoryReadabilityRulesListPreference = (ListPreference) findPreference(PREF_KEY_ROOM_HISTORY_READABILITY_LIST);
@@ -334,6 +342,8 @@ public void onCreate(Bundle savedInstanceState) {
mAdvandceSettingsCategory = (PreferenceCategory) getPreferenceManager().findPreference(PREF_KEY_ADVANCED);
mBannedMembersSettingsCategory = (PreferenceCategory) getPreferenceManager().findPreference(PREF_KEY_BANNED);
mBannedMembersSettingsCategoryDivider = (PreferenceCategory) getPreferenceManager().findPreference(PREF_KEY_BANNED_DIVIDER);
+ mFlairSettingsCategory = (PreferenceCategory) getPreferenceManager().findPreference(PREF_KEY_FLAIR);
+ mRoomNotificationsPreference = (ListPreference) getPreferenceManager().findPreference(PREF_KEY_ROOM_NOTIFICATIONS_LIST);
mRoomAccessRulesListPreference.setOnPreferenceWarningIconClickListener(new VectorListPreference.OnPreferenceWarningIconClickListener() {
@Override
@@ -518,6 +528,7 @@ public void onResume() {
updateRoomDirectoryVisibilityAsync();
refreshAddresses();
+ refreshFlair();
refreshBannedMembersList();
refreshEndToEnd();
}
@@ -698,10 +709,6 @@ private void updatePreferenceAccessFromPowerLevel() {
if (null != mRoomDirectoryVisibilitySwitch)
mRoomDirectoryVisibilitySwitch.setEnabled(isAdmin && isConnected);
- // room notification mute setting: no power condition
- if (null != mRoomMuteNotificationsSwitch)
- mRoomMuteNotificationsSwitch.setEnabled(isConnected);
-
// room tagging: no power condition
if (null != mRoomTagListPreference)
mRoomTagListPreference.setEnabled(isConnected);
@@ -713,8 +720,13 @@ private void updatePreferenceAccessFromPowerLevel() {
}
// room read history: admin only
- if (null != mRoomHistoryReadabilityRulesListPreference)
+ if (null != mRoomHistoryReadabilityRulesListPreference) {
mRoomHistoryReadabilityRulesListPreference.setEnabled(isAdmin && isConnected);
+ }
+
+ if (null != mRoomNotificationsPreference) {
+ mRoomNotificationsPreference.setEnabled(isConnected);
+ }
}
@@ -750,12 +762,6 @@ private void updatePreferenceUiValues() {
mRoomTopicEditTxt.setText(value);
}
- // update the mute notifications preference
- if (null != mRoomMuteNotificationsSwitch) {
- boolean isChecked = mBingRulesManager.isRoomNotificationsDisabled(mRoom.getRoomId());
- mRoomMuteNotificationsSwitch.setChecked(isChecked);
- }
-
// update room directory visibility
// if(null != mRoomDirectoryVisibilitySwitch) {
// boolean isRoomPublic = TextUtils.equals(mRoom.getVisibility()/*getLiveState().visibility ou .isPublic()*/, RoomState.DIRECTORY_VISIBILITY_PUBLIC);
@@ -812,6 +818,23 @@ private void updatePreferenceUiValues() {
}
}
+ if (null != mRoomNotificationsPreference) {
+ BingRulesManager.RoomNotificationState state = mSession.getDataHandler().getBingRulesManager().getRoomNotificationState(mRoom.getRoomId());
+
+ if (state == BingRulesManager.RoomNotificationState.ALL_MESSAGES_NOISY) {
+ value = getString(R.string.room_settings_all_messages_noisy);
+ } else if (state == BingRulesManager.RoomNotificationState.ALL_MESSAGES) {
+ value = getString(R.string.room_settings_all_messages);
+ } else if (state == BingRulesManager.RoomNotificationState.MENTIONS_ONLY) {
+ value = getString(R.string.room_settings_mention_only);
+ } else {
+ value = getString(R.string.room_settings_mute);
+ }
+
+ mRoomNotificationsPreference.setValue(value);
+ mRoomNotificationsPreference.setSummary(value);
+ }
+
// update the room tag preference
if (null != mRoomTagListPreference) {
@@ -901,8 +924,8 @@ public void onSharedPreferenceChanged(SharedPreferences aSharedPreferences, Stri
onRoomNamePreferenceChanged();
} else if (aKey.equals(PREF_KEY_ROOM_TOPIC)) {
onRoomTopicPreferenceChanged();
- } else if (aKey.equals(PREF_KEY_ROOM_MUTE_NOTIFICATIONS_SWITCH)) {
- onRoomMuteNotificationsPreferenceChanged();
+ } else if (aKey.equals(PREF_KEY_ROOM_NOTIFICATIONS_LIST)) {
+ onRoomNotificationsPreferenceChanged();
} else if (aKey.equals(PREF_KEY_ROOM_DIRECTORY_VISIBILITY_SWITCH)) {
onRoomDirectoryVisibilityPreferenceChanged(); // TBT
} else if (aKey.equals(PREF_KEY_ROOM_TAG_LIST)) {
@@ -1062,34 +1085,43 @@ private void onRoomDirectoryVisibilityPreferenceChanged() {
}
/**
- * Action when enabling / disabling the rooms notifications.
*/
- private void onRoomMuteNotificationsPreferenceChanged() {
+ private void onRoomNotificationsPreferenceChanged() {
// sanity check
- if ((null == mRoom) || (null == mBingRulesManager) || (null == mRoomMuteNotificationsSwitch)) {
+ if ((null == mRoom) || (null == mBingRulesManager)) {
return;
}
- // get new and previous values
- boolean isNotificationsMuted = mRoomMuteNotificationsSwitch.isChecked();
- boolean previousValue = mBingRulesManager.isRoomNotificationsDisabled(mRoom.getRoomId());
+ String value = mRoomNotificationsPreference.getValue();
+ BingRulesManager.RoomNotificationState updatedState;
+
+ if (TextUtils.equals(value, getString(R.string.room_settings_all_messages_noisy))) {
+ updatedState = BingRulesManager.RoomNotificationState.ALL_MESSAGES_NOISY;
+ } else if (TextUtils.equals(value, getString(R.string.room_settings_all_messages))) {
+ updatedState = BingRulesManager.RoomNotificationState.ALL_MESSAGES;
+ } else if (TextUtils.equals(value, getString(R.string.room_settings_mention_only))) {
+ updatedState = BingRulesManager.RoomNotificationState.MENTIONS_ONLY;
+ } else {
+ updatedState = BingRulesManager.RoomNotificationState.MUTE;
+ }
// update only, if values are different
- if (isNotificationsMuted != previousValue) {
+ if (mBingRulesManager.getRoomNotificationState(mRoom.getRoomId()) != updatedState) {
displayLoadingView();
- mBingRulesManager.muteRoomNotifications(mRoom.getRoomId(), isNotificationsMuted, new BingRulesManager.onBingRuleUpdateListener() {
- @Override
- public void onBingRuleUpdateSuccess() {
- Log.d(LOG_TAG, "##onRoomMuteNotificationsPreferenceChanged(): update succeed");
- hideLoadingView(UPDATE_UI);
- }
+ mBingRulesManager.updateRoomNotificationState(mRoom.getRoomId(), updatedState,
+ new BingRulesManager.onBingRuleUpdateListener() {
+ @Override
+ public void onBingRuleUpdateSuccess() {
+ Log.d(LOG_TAG, "##onRoomNotificationsPreferenceChanged(): update succeed");
+ hideLoadingView(UPDATE_UI);
+ }
- @Override
- public void onBingRuleUpdateFailure(String errorMessage) {
- Log.w(LOG_TAG, "##onRoomMuteNotificationsPreferenceChanged(): BingRuleUpdateFailure");
- hideLoadingView(DO_NOT_UPDATE_UI);
- }
- });
+ @Override
+ public void onBingRuleUpdateFailure(String errorMessage) {
+ Log.w(LOG_TAG, "##onRoomNotificationsPreferenceChanged(): BingRuleUpdateFailure");
+ hideLoadingView(DO_NOT_UPDATE_UI);
+ }
+ });
}
}
@@ -1290,7 +1322,7 @@ private void refreshBannedMembersList() {
Collections.sort(bannedMembers, new Comparator() {
@Override
public int compare(RoomMember m1, RoomMember m2) {
- return m1.getUserId().toLowerCase().compareTo(m2.getUserId().toLowerCase());
+ return m1.getUserId().toLowerCase(VectorApp.getApplicationLocale()).compareTo(m2.getUserId().toLowerCase(VectorApp.getApplicationLocale()));
}
});
@@ -1329,6 +1361,151 @@ public boolean onPreferenceClick(Preference preference) {
}
}
+ //================================================================================
+ // flair management
+ //================================================================================
+
+ private final ApiCallback mFlairUpdatesCallback = new ApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ hideLoadingView(false);
+ refreshFlair();
+ }
+ });
+ }
+
+ /**
+ * Error management.
+ * @param errorMessage the error message
+ */
+ private void onError(final String errorMessage) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
+ hideLoadingView(false);
+ refreshFlair();
+ }
+ });
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onError(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onError(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onError(e.getLocalizedMessage());
+ }
+ };
+
+ /**
+ * Tells if the current user can updates the related group aka flairs
+ *
+ * @return true if the user is allowed.
+ */
+ private boolean canUpdateFlair() {
+ boolean canUpdateAliases = false;
+
+ PowerLevels powerLevels = mRoom.getLiveState().getPowerLevels();
+
+ if (null != powerLevels) {
+ int powerLevel = powerLevels.getUserPowerLevel(mSession.getMyUserId());
+ canUpdateAliases = powerLevel >= powerLevels.minimumPowerLevelForSendingEventAsStateEvent(Event.EVENT_TYPE_STATE_RELATED_GROUPS);
+ }
+
+ return canUpdateAliases;
+ }
+
+ /**
+ * Refresh the flair list
+ */
+ private void refreshFlair() {
+ final List groups = mRoom.getLiveState().getRelatedGroups();
+ Collections.sort(groups, String.CASE_INSENSITIVE_ORDER);
+
+ mFlairSettingsCategory.removeAll();
+
+ if (!groups.isEmpty()) {
+ for (final String groupId : groups) {
+ VectorCustomActionEditTextPreference preference = new VectorCustomActionEditTextPreference(getActivity());
+ preference.setTitle(groupId);
+ preference.setKey(FLAIR_PREFERENCE_KEY_BASE + groupId);
+
+ preference.setOnPreferenceLongClickListener(new VectorCustomActionEditTextPreference.OnPreferenceLongClickListener() {
+ @Override
+ public boolean onPreferenceLongClick(Preference preference) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ displayLoadingView();
+ mRoom.removeRelatedGroup(groupId, mFlairUpdatesCallback);
+ }
+ });
+
+ return true;
+ }
+ });
+ mFlairSettingsCategory.addPreference(preference);
+ }
+ } else {
+ VectorCustomActionEditTextPreference preference = new VectorCustomActionEditTextPreference(getActivity());
+ preference.setTitle(getString(R.string.room_settings_no_flair));
+ preference.setKey(FLAIR_PREFERENCE_KEY_BASE + "no_flair");
+
+ mFlairSettingsCategory.addPreference(preference);
+ }
+
+ if (canUpdateFlair()) {
+ // display the "add addresses" entry
+ EditTextPreference addAddressPreference = new EditTextPreference(getActivity());
+ addAddressPreference.setTitle(R.string.room_settings_add_new_group);
+ addAddressPreference.setDialogTitle(R.string.room_settings_add_new_group);
+ addAddressPreference.setKey(FLAIR_PREFERENCE_KEY_BASE + "__add");
+ addAddressPreference.setIcon(CommonActivityUtils.tintDrawable(getActivity(), ContextCompat.getDrawable(getActivity(), R.drawable.ic_add_black), R.attr.settings_icon_tint_color));
+
+ addAddressPreference.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final String groupId = ((String) newValue).trim();
+
+ // ignore empty alias
+ if (!TextUtils.isEmpty(groupId)) {
+ if (!MXSession.isGroupId(groupId)) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.room_settings_invalid_group_format_dialog_title);
+ builder.setMessage(getString(R.string.room_settings_invalid_group_format_dialog_body, groupId));
+ builder.setPositiveButton(R.string.ok, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ } else if (!groups.contains(groupId)) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ displayLoadingView();
+ mRoom.addRelatedGroup(groupId, mFlairUpdatesCallback);
+ }
+ });
+ }
+ }
+ return false;
+ }
+ });
+
+ mFlairSettingsCategory.addPreference(addAddressPreference);
+ }
+ }
+
//================================================================================
// Aliases management
//================================================================================
diff --git a/vector/src/main/java/im/vector/fragments/VectorSearchRoomFilesListFragment.java b/vector/src/main/java/im/vector/fragments/VectorSearchRoomFilesListFragment.java
index ce4550838e..50b594efe8 100644
--- a/vector/src/main/java/im/vector/fragments/VectorSearchRoomFilesListFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorSearchRoomFilesListFragment.java
@@ -28,7 +28,7 @@
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.Message;
+import org.matrix.androidsdk.rest.model.message.Message;
import org.matrix.androidsdk.rest.model.TokensChunkResponse;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.androidsdk.util.Log;
diff --git a/vector/src/main/java/im/vector/fragments/VectorSearchRoomsFilesListFragment.java b/vector/src/main/java/im/vector/fragments/VectorSearchRoomsFilesListFragment.java
index 9431cb7928..2608d81b6d 100644
--- a/vector/src/main/java/im/vector/fragments/VectorSearchRoomsFilesListFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorSearchRoomsFilesListFragment.java
@@ -27,8 +27,8 @@
import org.matrix.androidsdk.adapters.MessageRow;
import org.matrix.androidsdk.adapters.AbstractMessagesAdapter;
import org.matrix.androidsdk.rest.model.Event;
-import org.matrix.androidsdk.rest.model.FileMessage;
-import org.matrix.androidsdk.rest.model.Message;
+import org.matrix.androidsdk.rest.model.message.FileMessage;
+import org.matrix.androidsdk.rest.model.message.Message;
import org.matrix.androidsdk.util.JsonUtils;
import java.util.ArrayList;
diff --git a/vector/src/main/java/im/vector/fragments/VectorSearchRoomsListFragment.java b/vector/src/main/java/im/vector/fragments/VectorSearchRoomsListFragment.java
index 4c6faa496c..80681c05ae 100644
--- a/vector/src/main/java/im/vector/fragments/VectorSearchRoomsListFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorSearchRoomsListFragment.java
@@ -32,7 +32,7 @@
import org.matrix.androidsdk.fragments.MatrixMessageListFragment;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.PublicRoom;
+import org.matrix.androidsdk.rest.model.publicroom.PublicRoom;
import java.util.List;
@@ -43,7 +43,6 @@
import im.vector.activity.VectorPublicRoomsActivity;
import im.vector.activity.VectorRoomActivity;
import im.vector.adapters.VectorRoomSummaryAdapter;
-import im.vector.view.RecentsExpandableListView;
public class VectorSearchRoomsListFragment extends VectorRecentsListFragment {
// the session
diff --git a/vector/src/main/java/im/vector/fragments/VectorSettingsPreferencesFragment.java b/vector/src/main/java/im/vector/fragments/VectorSettingsPreferencesFragment.java
index e47303ea09..8f319aacbc 100755
--- a/vector/src/main/java/im/vector/fragments/VectorSettingsPreferencesFragment.java
+++ b/vector/src/main/java/im/vector/fragments/VectorSettingsPreferencesFragment.java
@@ -24,6 +24,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -38,6 +39,7 @@
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
import android.provider.Settings;
import android.support.design.widget.TextInputEditText;
import android.support.v4.content.ContextCompat;
@@ -57,7 +59,6 @@
import android.widget.TextView;
import android.widget.Toast;
-import com.google.gson.JsonElement;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
@@ -73,11 +74,13 @@
import org.matrix.androidsdk.listeners.MXMediaUploadListener;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
-import org.matrix.androidsdk.rest.model.DeviceInfo;
-import org.matrix.androidsdk.rest.model.DevicesListResponse;
+import org.matrix.androidsdk.rest.model.group.Group;
+import org.matrix.androidsdk.rest.model.search.SearchGroup;
+import org.matrix.androidsdk.rest.model.sync.DeviceInfo;
+import org.matrix.androidsdk.rest.model.sync.DevicesListResponse;
import org.matrix.androidsdk.rest.model.MatrixError;
-import org.matrix.androidsdk.rest.model.ThirdPartyIdentifier;
-import org.matrix.androidsdk.rest.model.ThreePid;
+import org.matrix.androidsdk.rest.model.pid.ThirdPartyIdentifier;
+import org.matrix.androidsdk.rest.model.pid.ThreePid;
import org.matrix.androidsdk.rest.model.bingrules.BingRule;
import org.matrix.androidsdk.rest.model.bingrules.BingRuleSet;
import org.matrix.androidsdk.util.BingRulesManager;
@@ -87,12 +90,14 @@
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import im.vector.Matrix;
import im.vector.R;
@@ -108,6 +113,8 @@
import im.vector.preference.ProgressBarPreference;
import im.vector.preference.UserAvatarPreference;
import im.vector.preference.VectorCustomActionEditTextPreference;
+import im.vector.preference.VectorGroupPreference;
+import im.vector.preference.VectorSwitchPreference;
import im.vector.util.PhoneNumberUtils;
import im.vector.util.PreferencesManager;
import im.vector.util.ThemeUtils;
@@ -200,6 +207,8 @@ public void onAccountInfoUpdate(MyUser myUser) {
private EditTextPreference mSyncRequestDelayPreference;
private PreferenceCategory mLabsCategory;
+ private PreferenceCategory mGroupsFlairCategory;
+
// static constructor
public static VectorSettingsPreferencesFragment newInstance(String matrixId) {
VectorSettingsPreferencesFragment f = new VectorSettingsPreferencesFragment();
@@ -497,6 +506,50 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
}
});
+ final VectorSwitchPreference urlPreviewPreference = (VectorSwitchPreference)findPreference(PreferencesManager.SETTINGS_SHOW_URL_PREVIEW_KEY);
+ urlPreviewPreference.setChecked(mSession.isURLPreviewEnabled());
+
+ urlPreviewPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+
+ if ((null != newValue) && ((boolean)newValue != mSession.isURLPreviewEnabled())) {
+ displayLoadingView();
+ mSession.setURLPreviewStatus((boolean) newValue, new ApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ urlPreviewPreference.setChecked(mSession.isURLPreviewEnabled());
+ hideLoadingView();
+ }
+
+ private void onError(String errorMessage) {
+ if (null != getActivity()) {
+ Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
+ }
+ onSuccess(null);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onError(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onError(e.getLocalizedMessage());
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onError(e.getLocalizedMessage());
+ }
+ });
+ }
+
+ return false;
+ }
+ });
+
// push rules
for (String resourceText : mPushesRuleByResourceId.keySet()) {
final Preference preference = findPreference(resourceText);
@@ -514,7 +567,7 @@ public boolean onPreferenceChange(Preference preference, Object newValueAsVoid)
}
});
} else if (preference instanceof BingRulePreference) {
- final BingRulePreference bingRulePreference = (BingRulePreference)preference;
+ final BingRulePreference bingRulePreference = (BingRulePreference) preference;
bingRulePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -614,6 +667,7 @@ public void onThirdPartyUnregistrationFailed() {
mCryptographyCategory = (PreferenceCategory) findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY);
mCryptographyCategoryDivider = (PreferenceCategory) findPreference(PreferencesManager.SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY);
mLabsCategory = (PreferenceCategory) findPreference(PreferencesManager.SETTINGS_LABS_PREFERENCE_KEY);
+ mGroupsFlairCategory = (PreferenceCategory) findPreference(PreferencesManager.SETTINGS_GROUPS_FLAIR_KEY);
// preference to start the App info screen, to facilitate App permissions access
Preference applicationInfoLInkPref = findPreference(APP_INFO_LINK_PREFERENCE_KEY);
@@ -757,6 +811,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
refreshPhoneNumbersList();
refreshIgnoredUsersList();
refreshDevicesList();
+ refreshGroupFlairsList();
}
@Override
@@ -922,11 +977,11 @@ private void refreshDisplay() {
if (null != preference) {
if (preference instanceof BingRulePreference) {
- BingRulePreference bingRulePreference = (BingRulePreference)preference;
+ BingRulePreference bingRulePreference = (BingRulePreference) preference;
bingRulePreference.setEnabled((null != rules) && isConnected);
bingRulePreference.setBingRule(mSession.getDataHandler().pushRules().findDefaultRule(mPushesRuleByResourceId.get(resourceText)));
} else {
- CheckBoxPreference switchPreference = (CheckBoxPreference)preference;
+ CheckBoxPreference switchPreference = (CheckBoxPreference) preference;
if (resourceText.equals(PreferencesManager.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)) {
switchPreference.setChecked(gcmMgr.areDeviceNotificationsAllowed());
} else if (resourceText.equals(PreferencesManager.SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY)) {
@@ -1298,6 +1353,12 @@ public void onActivityResult(int requestCode, int resultCode, final Intent data)
switch (requestCode) {
case REQUEST_NOTIFICATION_RINGTONE: {
PreferencesManager.setNotificationRingTone(getActivity(), (Uri) data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI));
+
+ // test if the selected ring tone can be played
+ if (null == PreferencesManager.getNotificationRingToneName(getActivity())) {
+ PreferencesManager.setNotificationRingTone(getActivity(), PreferencesManager.getNotificationRingTone(getActivity()));
+ }
+
refreshNotificationRingTone();
break;
}
@@ -1388,7 +1449,7 @@ private void refreshPreferences() {
for (String resourceText : mPushesRuleByResourceId.keySet()) {
Preference preference = findPreference(resourceText);
- if ((null != preference) && (preference instanceof CheckBoxPreference)) {
+ if ((null != preference) && (preference instanceof CheckBoxPreference)) {
String ruleId = mPushesRuleByResourceId.get(resourceText);
BingRule rule = mBingRuleSet.findDefaultRule(ruleId);
@@ -1406,7 +1467,7 @@ else if (isEnabled) {
isEnabled = false;
} else if (1 == actions.size()) {
try {
- isEnabled = !TextUtils.equals((String)(actions.get(0)), BingRule.ACTION_DONT_NOTIFY);
+ isEnabled = !TextUtils.equals((String) (actions.get(0)), BingRule.ACTION_DONT_NOTIFY);
} catch (Exception e) {
Log.e(LOG_TAG, "## refreshPreferences failed " + e.getMessage());
}
@@ -1428,7 +1489,7 @@ else if (isEnabled) {
* @param preferenceSummary the displayed 3pid
*/
private void displayDelete3PIDConfirmationDialog(final ThirdPartyIdentifier pid, final CharSequence preferenceSummary) {
- final String mediumFriendlyName = ThreePid.getMediumFriendlyName(pid.medium, getActivity()).toLowerCase();
+ final String mediumFriendlyName = ThreePid.getMediumFriendlyName(pid.medium, getActivity()).toLowerCase(VectorApp.getApplicationLocale());
final String dialogMessage = getString(R.string.settings_delete_threepid_confirmation, mediumFriendlyName, preferenceSummary);
new AlertDialog.Builder(getActivity())
@@ -1495,7 +1556,7 @@ private void refreshIgnoredUsersList() {
Collections.sort(ignoredUsersList, new Comparator() {
@Override
public int compare(String u1, String u2) {
- return u1.toLowerCase().compareTo(u2.toLowerCase());
+ return u1.toLowerCase(VectorApp.getApplicationLocale()).compareTo(u2.toLowerCase(VectorApp.getApplicationLocale()));
}
});
@@ -2915,5 +2976,134 @@ public void onUnexpectedError(Exception e) {
}
}
+ //==============================================================================================================
+ // Group flairs management
+ //==============================================================================================================
+
+ /**
+ * Force the refresh of the devices list.
+ * The devices list is the list of the devices where the user as looged in.
+ * It can be any mobile device, as any browser.
+ */
+ private void refreshGroupFlairsList() {
+ if (null != mSession) {
+ // display a spinner while refreshing
+ if (0 == mGroupsFlairCategory.getPreferenceCount()) {
+ ProgressBarPreference preference = new ProgressBarPreference(getActivity());
+ mGroupsFlairCategory.addPreference(preference);
+ }
+
+ mSession.getGroupsManager().getUserPublicisedGroups(mSession.getMyUserId(), true, new ApiCallback>() {
+ @Override
+ public void onSuccess(Set publicisedGroups) {
+ buildGroupsList(publicisedGroups);
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ // NOP
+ }
+ @Override
+ public void onMatrixError(MatrixError e) {
+ // NOP
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ // NOP
+ }
+ });
+ }
+ }
+
+ // current publicised group list
+ private Set mPublicisedGroups = null;
+
+ /**
+ * Build the groups list.
+ *
+ * @param publicisedGroups the publicised groups list.
+ */
+ private void buildGroupsList(final Set publicisedGroups) {
+ boolean isNewList = true;
+
+ if ((null != mPublicisedGroups) && (mPublicisedGroups.size() == publicisedGroups.size())) {
+ isNewList = !mPublicisedGroups.containsAll(publicisedGroups);
+ }
+
+ if (isNewList) {
+ List joinedGroups = new ArrayList<>(mSession.getGroupsManager().getJoinedGroups());
+ Collections.sort(joinedGroups, Group.mGroupsComparator);
+
+ int prefIndex = 0;
+ mPublicisedGroups = publicisedGroups;
+
+ // clear everything
+ mGroupsFlairCategory.removeAll();
+
+ for (final Group group : joinedGroups) {
+ final VectorGroupPreference vectorGroupPreference = new VectorGroupPreference(getActivity());
+ vectorGroupPreference.setKey(DEVICES_PREFERENCE_KEY_BASE + prefIndex);
+ prefIndex++;
+
+ vectorGroupPreference.setGroup(group, mSession);
+ vectorGroupPreference.setTitle(group.getDisplayName());
+ vectorGroupPreference.setSummary(group.getGroupId());
+
+ vectorGroupPreference.setChecked(publicisedGroups.contains(group.getGroupId()));
+ mGroupsFlairCategory.addPreference(vectorGroupPreference);
+
+ vectorGroupPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValueAsVoid) {
+ if (newValueAsVoid instanceof Boolean) {
+ final boolean newValue = (boolean) newValueAsVoid;
+ boolean isFlaired = mPublicisedGroups.contains(group.getGroupId());
+
+ if (newValue != isFlaired) {
+ displayLoadingView();
+ mSession.getGroupsManager().updateGroupPublicity(group.getGroupId(), newValue, new ApiCallback() {
+ @Override
+ public void onSuccess(Void info) {
+ hideLoadingView();
+ if (newValue) {
+ mPublicisedGroups.add(group.getGroupId());
+ } else {
+ mPublicisedGroups.remove(group.getGroupId());
+ }
+ }
+
+ private void onError() {
+ hideLoadingView();
+ // restore default value
+ vectorGroupPreference.setChecked(publicisedGroups.contains(group.getGroupId()));
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onError();
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onError();
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onError();
+ }
+ });
+ }
+ }
+ return true;
+ }
+ });
+
+ }
+
+ refreshCryptographyPreference(mMyDeviceInfo);
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/fragments/VectorUserGroupsDialogFragment.java b/vector/src/main/java/im/vector/fragments/VectorUserGroupsDialogFragment.java
new file mode 100644
index 0000000000..5c0604cd3f
--- /dev/null
+++ b/vector/src/main/java/im/vector/fragments/VectorUserGroupsDialogFragment.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2015 OpenMarket Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.fragments;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import org.matrix.androidsdk.MXSession;
+import org.matrix.androidsdk.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import im.vector.Matrix;
+import im.vector.R;
+import im.vector.adapters.VectorGroupsListAdapter;
+
+/**
+ * A dialog fragment showing the group ids list
+ */
+public class VectorUserGroupsDialogFragment extends DialogFragment {
+ private static final String LOG_TAG = VectorUserGroupsDialogFragment.class.getSimpleName();
+
+ private static final String ARG_SESSION_ID = "ARG_SESSION_ID";
+ private static final String ARG_USER_ID = "ARG_USER_ID";
+ private static final String ARG_GROUPS_ID = "ARG_GROUPS_ID";
+
+ public static VectorUserGroupsDialogFragment newInstance(String sessionId, String userId, List groupIds) {
+ VectorUserGroupsDialogFragment f = new VectorUserGroupsDialogFragment();
+ Bundle args = new Bundle();
+ args.putString(ARG_SESSION_ID, sessionId);
+ args.putString(ARG_USER_ID, userId);
+ args.putStringArrayList(ARG_GROUPS_ID, new ArrayList<>(groupIds));
+ f.setArguments(args);
+ return f;
+ }
+
+ private MXSession mSession;
+ private String mUserId;
+ private ArrayList mGroupIds;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSession = Matrix.getInstance(getContext()).getSession(getArguments().getString(ARG_SESSION_ID));
+ mUserId = getArguments().getString(ARG_USER_ID);
+ mGroupIds = getArguments().getStringArrayList(ARG_GROUPS_ID);
+
+ // sanity check
+ if ((mSession == null) || TextUtils.isEmpty(mUserId)) {
+ Log.e(LOG_TAG, "## onCreate() : invalid parameters");
+ dismiss();
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ super.onCreateView(inflater, container, savedInstanceState);
+ View v = inflater.inflate(R.layout.fragment_dialog_groups_list, container, false);
+ ListView listView = v.findViewById(R.id.listView_groups);
+
+ final VectorGroupsListAdapter adapter = new VectorGroupsListAdapter(getActivity(), R.layout.adapter_item_group_view, mSession);
+ adapter.addAll(mGroupIds);
+ listView.setAdapter(adapter);
+
+ return v;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog d = super.onCreateDialog(savedInstanceState);
+ d.setTitle(getString(R.string.groups_list));
+ return d;
+ }
+}
diff --git a/vector/src/main/java/im/vector/gcm/GcmRegistrationManager.java b/vector/src/main/java/im/vector/gcm/GcmRegistrationManager.java
index 6b8de7c922..244e3d0c5c 100755
--- a/vector/src/main/java/im/vector/gcm/GcmRegistrationManager.java
+++ b/vector/src/main/java/im/vector/gcm/GcmRegistrationManager.java
@@ -20,13 +20,16 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
+import org.matrix.androidsdk.HomeServerConnectionConfig;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
+import org.matrix.androidsdk.rest.client.PushersRestClient;
import org.matrix.androidsdk.util.Log;
import org.matrix.androidsdk.MXSession;
@@ -42,7 +45,9 @@
import im.vector.util.PreferencesManager;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
@@ -133,8 +138,8 @@ private enum RegistrationState {
// 3 states : null not initialized (retrieved by flavor)
private static Boolean mUseGCM;
- //
- private boolean mLastBatteryOptimizationStatus;
+ // pusher rest client
+ private Map mPushersRestClients = new HashMap<>();
/**
* Constructor
@@ -173,10 +178,40 @@ public void onNetworkConnectionUpdate(boolean isConnected) {
});
mRegistrationState = getStoredRegistrationState();
- mLastBatteryOptimizationStatus = PreferencesManager.canStartBackgroundService(mContext);
mRegistrationToken = getStoredRegistrationToken();
}
+ /**
+ * Retrieves the pushers rest client.
+ *
+ * @param session the session
+ * @return the pushers rest client.
+ */
+ private PushersRestClient getPushersRestClient(MXSession session) {
+ PushersRestClient pushersRestClient = mPushersRestClients.get(session.getMyUserId());
+
+ if (null == pushersRestClient) {
+ // pusher uses a custom server
+ if (!TextUtils.isEmpty(mContext.getString(R.string.push_server_url))) {
+ try {
+ HomeServerConnectionConfig hsConfig = new HomeServerConnectionConfig(Uri.parse(mContext.getString(R.string.push_server_url)));
+ hsConfig.setCredentials(session.getCredentials());
+ pushersRestClient = new PushersRestClient(hsConfig);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## getPushersRestClient() failed " + e.getMessage());
+ }
+ }
+
+ if (null == pushersRestClient) {
+ pushersRestClient = session.getPushersRestClient();
+ }
+
+ mPushersRestClients.put(session.getMyUserId(), pushersRestClient);
+ }
+
+ return pushersRestClient;
+ }
+
/**
* Check if the GCM registration has been broken with a new token ID.
* The GCM could have cleared it (onTokenRefresh).
@@ -496,8 +531,7 @@ public void onThirdPartyUnregistrationFailed() {
* @return true if the registration was done with event Id only
*/
public void onAppResume() {
- if ((mRegistrationState == RegistrationState.SERVER_REGISTERED) &&
- (mLastBatteryOptimizationStatus != PreferencesManager.canStartBackgroundService(mContext))) {
+ if (mRegistrationState == RegistrationState.SERVER_REGISTERED) {
Log.d(LOG_TAG, "## onAppResume() : force the GCM registration");
forceSessionsRegistration(new ThirdPartyRegistrationListener() {
@@ -558,9 +592,9 @@ private void registerToThirdPartyServer(final MXSession session, final boolean a
Log.d(LOG_TAG, "registerToThirdPartyServer of " + session.getMyUserId());
- boolean eventIdOnlyPushes = isBackgroundSyncAllowed() && PreferencesManager.canStartBackgroundService(mContext);
+ boolean eventIdOnlyPushes = isBackgroundSyncAllowed();
- session.getPushersRestClient()
+ getPushersRestClient(session)
.addHttpPusher(mRegistrationToken, DEFAULT_PUSHER_APP_ID, computePushTag(session),
mPusherLang, mPusherAppName, mBasePusherDeviceName,
DEFAULT_PUSHER_URL, append, eventIdOnlyPushes, new ApiCallback() {
@@ -633,7 +667,7 @@ public void onUnexpectedError(Exception e) {
*/
public void refreshPushersList(List sessions, final ApiCallback callback) {
if ((null != sessions) && (sessions.size() > 0)) {
- sessions.get(0).getPushersRestClient().getPushers(new ApiCallback() {
+ getPushersRestClient(sessions.get(0)).getPushers(new ApiCallback() {
@Override
public void onSuccess(PushersResponse pushersResponse) {
@@ -880,9 +914,10 @@ public void onThirdPartyUnregistrationFailed() {
* @param callback the asynchronous callback
*/
public void unregister(final MXSession session, final Pusher pusher, final ApiCallback callback) {
- session.getPushersRestClient().removeHttpPusher(pusher.pushkey, pusher.appId, pusher.profileTag, pusher.lang, pusher.appDisplayName, pusher.deviceDisplayName, pusher.data.get("url"), new ApiCallback() {
+ getPushersRestClient(session).removeHttpPusher(pusher.pushkey, pusher.appId, pusher.profileTag, pusher.lang, pusher.appDisplayName, pusher.deviceDisplayName, pusher.data.get("url"), new ApiCallback() {
@Override
public void onSuccess(Void info) {
+ mPushersRestClients.remove(session.getMyUserId());
refreshPushersList(new ArrayList<>(Matrix.getInstance(mContext).getSessions()), callback);
}
@@ -896,6 +931,7 @@ public void onNetworkError(Exception e) {
@Override
public void onMatrixError(MatrixError e) {
if (e.mStatus == 404) {
+ mPushersRestClients.remove(session.getMyUserId());
// httpPusher is not available on server side anymore so assume the removal was successful
onSuccess(null);
return;
@@ -923,7 +959,7 @@ public void onUnexpectedError(Exception e) {
public void unregister(final MXSession session, final ThirdPartyRegistrationListener listener) {
Log.d(LOG_TAG, "unregister " + session.getMyUserId());
- session.getPushersRestClient()
+ getPushersRestClient(session)
.removeHttpPusher(mRegistrationToken, DEFAULT_PUSHER_APP_ID, computePushTag(session),
mPusherLang, mPusherAppName, mBasePusherDeviceName,
DEFAULT_PUSHER_URL, new ApiCallback() {
@@ -1128,7 +1164,7 @@ public void setBackgroundSyncAllowed(boolean isAllowed) {
* @return the sync timeout in ms.
*/
public int getBackgroundSyncTimeOut() {
- return getGcmSharedPreferences().getInt(PREFS_SYNC_TIMEOUT, 30000);
+ return getGcmSharedPreferences().getInt(PREFS_SYNC_TIMEOUT, 6000);
}
/**
@@ -1146,10 +1182,10 @@ public void setBackgroundSyncTimeOut(int syncDelay) {
* @return the delay between two syncs in ms.
*/
public int getBackgroundSyncDelay() {
- // on fdroid version, the default sync delay is about 10 minutes
+ // on fdroid version, the default sync delay is about 1 minutes
// set a large value because many users don't know it can be defined from the settings page
if ((null == mRegistrationToken) && (null == getStoredRegistrationToken()) && !getGcmSharedPreferences().contains(PREFS_SYNC_DELAY)) {
- return 10 * 60 * 1000;
+ return 60 * 1000;
} else {
int currentValue = 0;
MXSession session = Matrix.getInstance(mContext).getDefaultSession();
diff --git a/vector/src/main/java/im/vector/listeners/IMessagesAdapterActionsListener.java b/vector/src/main/java/im/vector/listeners/IMessagesAdapterActionsListener.java
index 1f81493279..0a36786640 100755
--- a/vector/src/main/java/im/vector/listeners/IMessagesAdapterActionsListener.java
+++ b/vector/src/main/java/im/vector/listeners/IMessagesAdapterActionsListener.java
@@ -21,6 +21,8 @@
import org.matrix.androidsdk.crypto.data.MXDeviceInfo;
import org.matrix.androidsdk.rest.model.Event;
+import java.util.List;
+
/**
* Actions listeners
*/
@@ -92,6 +94,14 @@ public interface IMessagesAdapterActionsListener {
*/
void onMoreReadReceiptClick(String eventId);
+ /**
+ * Define the action to perform when the group flairs is clicked.
+ *
+ * @param userId the user id
+ * @param groupIds the group ids list
+ */
+ void onGroupFlairClick(String userId, List groupIds);
+
/**
* An url has been clicked in a message text.
*
@@ -135,6 +145,13 @@ public interface IMessagesAdapterActionsListener {
*/
void onMessageIdClick(String messageId);
+ /**
+ * A group id has been clicked in a message body.
+ *
+ * @param groupId the group id.
+ */
+ void onGroupIdClick(String groupId);
+
/**
* The required indexes are not anymore valid.
*/
diff --git a/vector/src/main/java/im/vector/util/NotificationUtils.java b/vector/src/main/java/im/vector/notifications/NotificationUtils.java
similarity index 62%
rename from vector/src/main/java/im/vector/util/NotificationUtils.java
rename to vector/src/main/java/im/vector/notifications/NotificationUtils.java
index 88c371bbfd..7824c14b66 100755
--- a/vector/src/main/java/im/vector/util/NotificationUtils.java
+++ b/vector/src/main/java/im/vector/notifications/NotificationUtils.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package im.vector.util;
+package im.vector.notifications;
import android.annotation.SuppressLint;
import android.app.Notification;
@@ -38,16 +38,7 @@
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.StyleSpan;
-import android.widget.ImageView;
-
-import org.matrix.androidsdk.MXSession;
-import org.matrix.androidsdk.data.Room;
-import org.matrix.androidsdk.data.store.IMXStore;
-import org.matrix.androidsdk.rest.model.Event;
-import org.matrix.androidsdk.rest.model.RoomMember;
-import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.rest.model.bingrules.BingRule;
-import org.matrix.androidsdk.util.EventDisplay;
import org.matrix.androidsdk.util.Log;
import im.vector.Matrix;
@@ -60,11 +51,8 @@
import im.vector.activity.VectorHomeActivity;
import im.vector.activity.VectorRoomActivity;
import im.vector.receiver.DismissNotificationReceiver;
+import im.vector.util.PreferencesManager;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -82,37 +70,6 @@ public class NotificationUtils {
public static final String ACTION_MESSAGE_REPLY = "ACTION_MESSAGE_REPLY";
public static final String EXTRA_ROOM_ID = "EXTRA_ROOM_ID";
- /**
- * Retrieve the room name.
- *
- * @param session the session
- * @param room the room
- * @param event the event
- * @return the room name
- */
- public static String getRoomName(Context context, MXSession session, Room room, Event event) {
- String roomName = VectorUtils.getRoomDisplayName(context, session, room);
-
- // avoid displaying the room Id
- // try to find the sender display name
- if (TextUtils.equals(roomName, room.getRoomId())) {
- roomName = room.getName(session.getMyUserId());
-
- // avoid room Id as name
- if (TextUtils.equals(roomName, room.getRoomId()) && (null != event)) {
- User user = session.getDataHandler().getStore().getUser(event.sender);
-
- if (null != user) {
- roomName = user.displayname;
- } else {
- roomName = event.sender;
- }
- }
- }
-
- return roomName;
- }
-
// on devices >= android O, we need to define a channel for each notifications
public static final String LISTEN_FOR_EVENTS_NOTIFICATION_CHANNEL_ID = "LISTEN_FOR_EVENTS_NOTIFICATION_CHANNEL_ID";
@@ -357,159 +314,41 @@ else if (width > height) {
return resizedBitmap;
}
- /**
- * This class manages the notification display.
- * It contains the message to display and its timestamp
- */
- static class NotificationDisplay {
- final long mEventTs;
- final SpannableString mMessage;
-
- NotificationDisplay(long ts, SpannableString message) {
- mEventTs = ts;
- mMessage = message;
- }
- }
-
- /**
- * NotificationDisplay comparator
- */
- private static final Comparator mNotificationDisplaySort = new Comparator() {
- @Override
- public int compare(NotificationDisplay lhs, NotificationDisplay rhs) {
- long t0 = lhs.mEventTs;
- long t1 = rhs.mEventTs;
-
- if (t0 > t1) {
- return -1;
- } else if (t0 < t1) {
- return +1;
- }
- return 0;
- }
- };
-
- /**
- * Define a notified event
- * i.e the matched bing rules
- */
- public static class NotifiedEvent {
- public final BingRule mBingRule;
- public final String mRoomId;
- public final String mEventId;
- public final long mOriginServerTs;
-
- public NotifiedEvent(String roomId, String eventId, BingRule bingRule, long originServerTs) {
- mRoomId = roomId;
- mEventId = eventId;
- mBingRule = bingRule;
- mOriginServerTs = originServerTs;
- }
- }
-
- // max number of lines to display the notification text styles
- private static final int MAX_NUMBER_NOTIFICATION_LINES = 10;
-
/**
* Add a text style to a notification when there are several notified rooms.
*
- * @param context the context
- * @param builder the notification builder
- * @param notifiedEventsByRoomId the notified events by room ids
+ * @param context the context
+ * @param builder the notification builder
+ * @param roomsNotifications the rooms notifications
*/
private static void addTextStyleWithSeveralRooms(Context context,
NotificationCompat.Builder builder,
- NotifiedEvent eventToNotify,
- boolean isInvitationEvent,
- Map> notifiedEventsByRoomId) {
- // TODO manage multi accounts
- MXSession session = Matrix.getInstance(context).getDefaultSession();
- IMXStore store = session.getDataHandler().getStore();
+ RoomsNotifications roomsNotifications) {
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
- int sum = 0;
- int roomsCount = 0;
-
-
- List notificationsList = new ArrayList<>();
-
- for (String roomId : notifiedEventsByRoomId.keySet()) {
- Room room = session.getDataHandler().getRoom(roomId);
- String roomName = getRoomName(context, session, room, null);
-
- List notifiedEvents = notifiedEventsByRoomId.get(roomId);
- Event latestEvent = store.getEvent(notifiedEvents.get(notifiedEvents.size() - 1).mEventId, roomId);
-
- String text;
- String header;
-
- EventDisplay eventDisplay = new RiotEventDisplay(context, latestEvent, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(false);
-
- if (room.isInvited()) {
- header = roomName + ": ";
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
- text = !TextUtils.isEmpty(textualDisplay) ? textualDisplay.toString() : "";
- } else if (1 == notifiedEvents.size()) {
- eventDisplay = new RiotEventDisplay(context, latestEvent, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(false);
-
- header = roomName + ": " + room.getLiveState().getMemberName(latestEvent.getSender()) + " ";
-
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
- // the event might have been redacted
- if (!TextUtils.isEmpty(textualDisplay)) {
- text = textualDisplay.toString();
- } else {
- text = "";
- }
- } else {
- header = roomName + ": ";
- text = context.getString(R.string.notification_unread_notified_messages, notifiedEvents.size());
- }
-
- // ad the line if it makes sense
- if (!TextUtils.isEmpty(text)) {
- SpannableString notifiedLine = new SpannableString(header + text);
- notifiedLine.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, header.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- notificationsList.add(new NotificationDisplay(latestEvent.getOriginServerTs(), notifiedLine));
- sum += notifiedEvents.size();
- roomsCount++;
- }
- }
-
- Collections.sort(notificationsList, mNotificationDisplaySort);
-
- if (notificationsList.size() > MAX_NUMBER_NOTIFICATION_LINES) {
- notificationsList = notificationsList.subList(0, MAX_NUMBER_NOTIFICATION_LINES);
- }
-
- for (NotificationDisplay notificationDisplay : notificationsList) {
- inboxStyle.addLine(notificationDisplay.mMessage);
+ for (RoomNotifications roomNotifications : roomsNotifications.mRoomNotifications) {
+ SpannableString notifiedLine = new SpannableString(roomNotifications.mMessagesSummary);
+ notifiedLine.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, roomNotifications.mMessageHeader.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ inboxStyle.addLine(notifiedLine);
}
inboxStyle.setBigContentTitle(context.getString(R.string.riot_app_name));
- inboxStyle.setSummaryText(context.getString(R.string.notification_unread_notified_messages_in_room, sum, roomsCount));
+ inboxStyle.setSummaryText(roomsNotifications.mSummaryText);
builder.setStyle(inboxStyle);
TaskStackBuilder stackBuilderTap = TaskStackBuilder.create(context);
Intent roomIntentTap;
- // sanity check
- if ((null == eventToNotify) || TextUtils.isEmpty(eventToNotify.mRoomId)) {
- // Build the pending intent for when the notification is clicked
- roomIntentTap = new Intent(context, VectorHomeActivity.class);
- } else {
- // add the home page the activity stack
- stackBuilderTap.addNextIntentWithParentStack(new Intent(context, VectorHomeActivity.class));
- if (isInvitationEvent) {
- // for invitation the room preview must be displayed
- roomIntentTap = CommonActivityUtils.buildIntentPreviewRoom(session.getMyUserId(), eventToNotify.mRoomId, context, VectorFakeRoomPreviewActivity.class);
- } else {
- roomIntentTap = new Intent(context, VectorRoomActivity.class);
- roomIntentTap.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, eventToNotify.mRoomId);
- }
+ // add the home page the activity stack
+ stackBuilderTap.addNextIntentWithParentStack(new Intent(context, VectorHomeActivity.class));
+
+ if (roomsNotifications.mIsInvitationEvent) {
+ // for invitation the room preview must be displayed
+ roomIntentTap = CommonActivityUtils.buildIntentPreviewRoom(roomsNotifications.mSessionId, roomsNotifications.mRoomId, context, VectorFakeRoomPreviewActivity.class);
+ } else {
+ roomIntentTap = new Intent(context, VectorRoomActivity.class);
+ roomIntentTap.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomsNotifications.mRoomId);
}
// the action must be unique else the parameters are ignored
@@ -530,54 +369,6 @@ private static void addTextStyleWithSeveralRooms(Context context,
context.getString(R.string.bottom_action_home),
viewAllTask.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
}
-
- // wearable
- try {
- long ts = 0;
- Event latestEvent = null;
-
- // search the oldest message
- for (String roomId : notifiedEventsByRoomId.keySet()) {
- List notifiedEvents = notifiedEventsByRoomId.get(roomId);
- Event event = store.getEvent(notifiedEvents.get(notifiedEvents.size() - 1).mEventId, roomId);
-
- if ((null != event) && (event.getOriginServerTs() > ts)) {
- ts = event.getOriginServerTs();
- latestEvent = event;
- }
- }
-
- // if there is a valid latest message
- if (null != latestEvent) {
- Room room = store.getRoom(latestEvent.roomId);
-
- if (null != room) {
- EventDisplay eventDisplay = new RiotEventDisplay(context, latestEvent, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(false);
- String roomName = getRoomName(context, session, room, null);
-
- String message = roomName + ": " + room.getLiveState().getMemberName(latestEvent.getSender()) + " ";
-
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
-
- // the event might have been redacted
- if (!TextUtils.isEmpty(textualDisplay)) {
- message += textualDisplay.toString();
- }
-
- NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
- NotificationCompat.Action action =
- new NotificationCompat.Action.Builder(R.drawable.message_notification_transparent,
- message,
- stackBuilderTap.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT))
- .build();
- wearableExtender.addAction(action);
- builder.extend(wearableExtender);
- }
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "## addTextStyleWithSeveralRooms() : WearableExtender failed " + e.getMessage());
- }
}
/**
@@ -595,91 +386,53 @@ private static void addTextStyleWithSeveralRooms(Context context,
* - "Room Name : XX unread messages" if there are many unread messages
* - 'Room Name : Sender - Message body" if there is only one unread message.
*
- * @param context the context
- * @param builder the notification builder
- * @param eventToNotify the latest notified event
- * @param isInvitationEvent true if the notified event is an invitation
- * @param notifiedEventsByRoomId the notified events by room ids
+ * @param context the context
+ * @param builder the notification builder
+ * @param roomsNotifications the rooms notifications
*/
private static void addTextStyle(Context context,
NotificationCompat.Builder builder,
- NotifiedEvent eventToNotify,
- boolean isInvitationEvent,
- Map> notifiedEventsByRoomId) {
+ RoomsNotifications roomsNotifications) {
// nothing to do
- if (0 == notifiedEventsByRoomId.size()) {
+ if (0 == roomsNotifications.mRoomNotifications.size()) {
return;
}
// when there are several rooms, the text style is not the same
- if (notifiedEventsByRoomId.size() > 1) {
- addTextStyleWithSeveralRooms(context, builder, eventToNotify, isInvitationEvent, notifiedEventsByRoomId);
+ if (roomsNotifications.mRoomNotifications.size() > 1) {
+ addTextStyleWithSeveralRooms(context, builder, roomsNotifications);
return;
}
- // TODO manage multi accounts
- MXSession session = Matrix.getInstance(context).getDefaultSession();
- IMXStore store = session.getDataHandler().getStore();
+ SpannableString latestText = null;
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
- String roomId = notifiedEventsByRoomId.keySet().iterator().next();
-
- Room room = session.getDataHandler().getRoom(roomId);
- String roomName = getRoomName(context, session, room, null);
-
- List notifiedEvents = notifiedEventsByRoomId.get(roomId);
- int unreadCount = notifiedEvents.size();
-
- // the messages are sorted from the oldest to the latest
- Collections.reverse(notifiedEvents);
-
- if (notifiedEvents.size() > MAX_NUMBER_NOTIFICATION_LINES) {
- notifiedEvents = notifiedEvents.subList(0, MAX_NUMBER_NOTIFICATION_LINES);
+ for (CharSequence sequence : roomsNotifications.mReversedMessagesList) {
+ inboxStyle.addLine(latestText = new SpannableString(sequence));
}
- SpannableString latestText = null;
-
- for (NotifiedEvent notifiedEvent : notifiedEvents) {
- Event event = store.getEvent(notifiedEvent.mEventId, notifiedEvent.mRoomId);
- EventDisplay eventDisplay = new RiotEventDisplay(context, event, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(true);
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
-
- if (!TextUtils.isEmpty(textualDisplay)) {
- inboxStyle.addLine(latestText = new SpannableString(textualDisplay));
- }
- }
- inboxStyle.setBigContentTitle(roomName);
+ inboxStyle.setBigContentTitle(roomsNotifications.mContentTitle);
// adapt the notification display to the number of notified messages
- if ((1 == notifiedEvents.size()) && (null != latestText)) {
+ if ((1 == roomsNotifications.mReversedMessagesList.size()) && (null != latestText)) {
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(latestText));
} else {
- if (unreadCount > MAX_NUMBER_NOTIFICATION_LINES) {
- inboxStyle.setSummaryText(context.getString(R.string.notification_unread_notified_messages, unreadCount));
+ if (!TextUtils.isEmpty(roomsNotifications.mSummaryText)) {
+ inboxStyle.setSummaryText(roomsNotifications.mSummaryText);
}
-
builder.setStyle(inboxStyle);
}
// do not offer to quick respond if the user did not dismiss the previous one
if (!LockScreenActivity.isDisplayingALockScreenActivity()) {
- if (!isInvitationEvent) {
- Event event = store.getEvent(eventToNotify.mEventId, eventToNotify.mRoomId);
- RoomMember member = room.getMember(event.getSender());
+ if (!roomsNotifications.mIsInvitationEvent) {
// offer to type a quick answer (i.e. without launching the application)
Intent quickReplyIntent = new Intent(context, LockScreenActivity.class);
- quickReplyIntent.putExtra(LockScreenActivity.EXTRA_ROOM_ID, roomId);
- quickReplyIntent.putExtra(LockScreenActivity.EXTRA_SENDER_NAME, (null == member) ? event.getSender() : member.getName());
-
- EventDisplay eventDisplay = new RiotEventDisplay(context, event, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(false);
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
- String body = !TextUtils.isEmpty(textualDisplay) ? textualDisplay.toString() : "";
-
- quickReplyIntent.putExtra(LockScreenActivity.EXTRA_MESSAGE_BODY, body);
+ quickReplyIntent.putExtra(LockScreenActivity.EXTRA_ROOM_ID, roomsNotifications.mRoomId);
+ quickReplyIntent.putExtra(LockScreenActivity.EXTRA_SENDER_NAME, roomsNotifications.mSenderName);
+ quickReplyIntent.putExtra(LockScreenActivity.EXTRA_MESSAGE_BODY, roomsNotifications.mQuickReplyBody);
// the action must be unique else the parameters are ignored
quickReplyIntent.setAction(QUICK_LAUNCH_ACTION + ((int) (System.currentTimeMillis())));
@@ -692,8 +445,8 @@ private static void addTextStyle(Context context,
{
// offer to type a quick reject button
Intent leaveIntent = new Intent(context, JoinScreenActivity.class);
- leaveIntent.putExtra(JoinScreenActivity.EXTRA_ROOM_ID, roomId);
- leaveIntent.putExtra(JoinScreenActivity.EXTRA_MATRIX_ID, session.getMyUserId());
+ leaveIntent.putExtra(JoinScreenActivity.EXTRA_ROOM_ID, roomsNotifications.mRoomId);
+ leaveIntent.putExtra(JoinScreenActivity.EXTRA_MATRIX_ID, roomsNotifications.mSessionId);
leaveIntent.putExtra(JoinScreenActivity.EXTRA_REJECT, true);
// the action must be unique else the parameters are ignored
@@ -708,8 +461,8 @@ private static void addTextStyle(Context context,
{
// offer to type a quick accept button
Intent acceptIntent = new Intent(context, JoinScreenActivity.class);
- acceptIntent.putExtra(JoinScreenActivity.EXTRA_ROOM_ID, roomId);
- acceptIntent.putExtra(JoinScreenActivity.EXTRA_MATRIX_ID, session.getMyUserId());
+ acceptIntent.putExtra(JoinScreenActivity.EXTRA_ROOM_ID, roomsNotifications.mRoomId);
+ acceptIntent.putExtra(JoinScreenActivity.EXTRA_MATRIX_ID, roomsNotifications.mSessionId);
acceptIntent.putExtra(JoinScreenActivity.EXTRA_JOIN, true);
// the action must be unique else the parameters are ignored
@@ -725,12 +478,12 @@ private static void addTextStyle(Context context,
// Build the pending intent for when the notification is clicked
Intent roomIntentTap;
- if (isInvitationEvent) {
+ if (roomsNotifications.mIsInvitationEvent) {
// for invitation the room preview must be displayed
- roomIntentTap = CommonActivityUtils.buildIntentPreviewRoom(session.getMyUserId(), roomId, context, VectorFakeRoomPreviewActivity.class);
+ roomIntentTap = CommonActivityUtils.buildIntentPreviewRoom(roomsNotifications.mSessionId, roomsNotifications.mRoomId, context, VectorFakeRoomPreviewActivity.class);
} else {
roomIntentTap = new Intent(context, VectorRoomActivity.class);
- roomIntentTap.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId);
+ roomIntentTap.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomsNotifications.mRoomId);
}
// the action must be unique else the parameters are ignored
roomIntentTap.setAction(TAP_TO_VIEW_ACTION + ((int) (System.currentTimeMillis())));
@@ -748,33 +501,16 @@ private static void addTextStyle(Context context,
stackBuilderTap.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
// wearable
- if (!isInvitationEvent) {
+ if (!roomsNotifications.mIsInvitationEvent) {
try {
- Event latestEvent = store.getEvent(notifiedEvents.get(notifiedEvents.size() - 1).mEventId, roomId);
-
- // if there is a valid latest message
- if (null != latestEvent) {
- EventDisplay eventDisplay = new RiotEventDisplay(context, latestEvent, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(false);
-
- String message = roomName + ": " + room.getLiveState().getMemberName(latestEvent.getSender()) + " ";
-
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
-
- // the event might have been redacted
- if (!TextUtils.isEmpty(textualDisplay)) {
- message += textualDisplay.toString();
- }
-
- NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
- NotificationCompat.Action action =
- new NotificationCompat.Action.Builder(R.drawable.message_notification_transparent,
- message,
- stackBuilderTap.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT))
- .build();
- wearableExtender.addAction(action);
- builder.extend(wearableExtender);
- }
+ NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
+ NotificationCompat.Action action =
+ new NotificationCompat.Action.Builder(R.drawable.message_notification_transparent,
+ roomsNotifications.mWearableMessage,
+ stackBuilderTap.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT))
+ .build();
+ wearableExtender.addAction(action);
+ builder.extend(wearableExtender);
} catch (Exception e) {
Log.e(LOG_TAG, "## addTextStyleWithSeveralRooms() : WearableExtender failed " + e.getMessage());
}
@@ -827,6 +563,30 @@ private static void manageNotificationSound(Context context, NotificationCompat.
}
}
+ /**
+ * Build a notification from the cached RoomsNotifications instance.
+ *
+ * @param context the context
+ * @param isBackground true if it is background notification
+ * @return the notification
+ */
+ public static Notification buildMessageNotification(Context context, boolean isBackground) {
+
+ Notification notification = null;
+ try {
+ RoomsNotifications roomsNotifications = RoomsNotifications.loadRoomsNotifications(context);
+
+ if (null != roomsNotifications) {
+ notification = buildMessageNotification(context, roomsNotifications, new BingRule(), isBackground);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## buildMessageNotification() : failed " + e.getMessage());
+ }
+
+ return notification;
+ }
+
+
/**
* Build a notification
*
@@ -840,81 +600,60 @@ public static Notification buildMessageNotification(Context context,
Map> notifiedEventsByRoomId,
NotifiedEvent eventToNotify,
boolean isBackground) {
- try {
- // TODO manage multi accounts
- MXSession session = Matrix.getInstance(context).getDefaultSession();
- IMXStore store = session.getDataHandler().getStore();
- if (null == store) {
- Log.e(LOG_TAG, "## buildMessageNotification() : null store");
- return null;
- }
-
- Room room = store.getRoom(eventToNotify.mRoomId);
- Event event = store.getEvent(eventToNotify.mEventId, eventToNotify.mRoomId);
-
- // sanity check
- if ((null == room) || (null == event)) {
- if (null == room) {
- Log.e(LOG_TAG, "## buildMessageNotification() : null room " + eventToNotify.mRoomId);
- } else {
- Log.e(LOG_TAG, "## buildMessageNotification() : null event " + eventToNotify.mEventId + " " + eventToNotify.mRoomId);
- }
- return null;
- }
-
- BingRule bingRule = eventToNotify.mBingRule;
-
- boolean isInvitationEvent = false;
+ Notification notification = null;
+ try {
+ RoomsNotifications roomsNotifications = new RoomsNotifications(eventToNotify, notifiedEventsByRoomId);
+ notification = buildMessageNotification(context, roomsNotifications, eventToNotify.mBingRule, isBackground);
+ // cache the value
+ RoomsNotifications.saveRoomNotifications(context, roomsNotifications);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## buildMessageNotification() : failed " + e.getMessage());
+ }
- EventDisplay eventDisplay = new RiotEventDisplay(context, event, room.getLiveState());
- eventDisplay.setPrependMessagesWithAuthor(true);
- CharSequence textualDisplay = eventDisplay.getTextualDisplay();
- String body = !TextUtils.isEmpty(textualDisplay) ? textualDisplay.toString() : "";
+ return notification;
+ }
- if (Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(event.getType())) {
- try {
- isInvitationEvent = "invite".equals(event.getContentAsJsonObject().getAsJsonPrimitive("membership").getAsString());
- } catch (Exception e) {
- Log.e(LOG_TAG, "prepareNotification : invitation parsing failed");
- }
- }
+ /**
+ * Build a notification
+ *
+ * @param context the context
+ * @param roomsNotifications the rooms notifications
+ * @param bingRule the bing rule
+ * @param isBackground true if it is background notification
+ * @return the notification
+ */
+ private static Notification buildMessageNotification(Context context,
+ RoomsNotifications roomsNotifications,
+ BingRule bingRule,
+ boolean isBackground) {
+ try {
Bitmap largeBitmap = null;
// when the event is an invitation one
// don't check if the sender ID is known because the members list are not yet downloaded
- if (!isInvitationEvent) {
+ if (!roomsNotifications.mIsInvitationEvent) {
// is there any avatar url
- if (!TextUtils.isEmpty(room.getAvatarUrl())) {
- int size = context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size);
-
- // check if the thumbnail is already downloaded
- File f = session.getMediasCache().thumbnailCacheFile(room.getAvatarUrl(), size);
-
- if (null != f) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- try {
- largeBitmap = BitmapFactory.decodeFile(f.getPath(), options);
- } catch (OutOfMemoryError oom) {
- Log.e(LOG_TAG, "decodeFile failed with an oom");
- }
- } else {
- session.getMediasCache().loadAvatarThumbnail(session.getHomeServerConfig(), new ImageView(context), room.getAvatarUrl(), size);
+ if (!TextUtils.isEmpty(roomsNotifications.mRoomAvatarPath)) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ try {
+ largeBitmap = BitmapFactory.decodeFile(roomsNotifications.mRoomAvatarPath, options);
+ } catch (OutOfMemoryError oom) {
+ Log.e(LOG_TAG, "decodeFile failed with an oom");
}
}
}
Log.d(LOG_TAG, "prepareNotification : with sound " + bingRule.isDefaultNotificationSound(bingRule.getNotificationSound()));
- String roomName = getRoomName(context, session, room, event);
-
addNotificationChannels(context);
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID);
- builder.setWhen(event.getOriginServerTs());
- builder.setContentTitle(roomName);
- builder.setContentText(body);
+ builder.setWhen(roomsNotifications.mContentTs);
+ builder.setContentTitle(roomsNotifications.mContentTitle);
+ builder.setContentText(roomsNotifications.mContentText);
builder.setGroup(context.getString(R.string.riot_app_name));
builder.setGroupSummary(true);
@@ -922,14 +661,14 @@ public static Notification buildMessageNotification(Context context,
builder.setDeleteIntent(PendingIntent.getBroadcast(context.getApplicationContext(), 0, new Intent(context.getApplicationContext(), DismissNotificationReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT));
try {
- addTextStyle(context, builder, eventToNotify, isInvitationEvent, notifiedEventsByRoomId);
+ addTextStyle(context, builder, roomsNotifications);
} catch (Exception e) {
Log.e(LOG_TAG, "## buildMessageNotification() : addTextStyle failed " + e.getMessage());
}
// only one room : display the large bitmap (it should be the room avatar
// several rooms : display the Riot avatar
- if (notifiedEventsByRoomId.keySet().size() == 1) {
+ if (roomsNotifications.mRoomNotifications.size() == 1) {
if (null != largeBitmap) {
largeBitmap = NotificationUtils.createSquareBitmap(largeBitmap);
builder.setLargeIcon(largeBitmap);
@@ -967,7 +706,7 @@ public static Notification buildMessagesListNotification(Context context, List mRoomNotificationsComparator = new Comparator() {
+ @Override
+ public int compare(RoomNotifications lhs, RoomNotifications rhs) {
+ long t0 = lhs.mLatestEventTs;
+ long t1 = rhs.mLatestEventTs;
+
+ if (t0 > t1) {
+ return -1;
+ } else if (t0 < t1) {
+ return +1;
+ }
+ return 0;
+ }
+ };
+
+ // empty constructor
+ public RoomNotifications() {
+ }
+
+ /*
+ * *********************************************************************************************
+ * Parcelable
+ * *********************************************************************************************
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mRoomId);
+ out.writeString(mRoomName);
+ out.writeString(mMessageHeader);
+ TextUtils.writeToParcel(mMessagesSummary, out, 0);
+ out.writeLong(mLatestEventTs);
+
+ out.writeString(mSenderName);
+ out.writeInt(mUnreadMessagesCount);
+ }
+
+ /**
+ * Creator from a parcel
+ *
+ * @param in the parcel
+ */
+ private RoomNotifications(Parcel in) {
+ mRoomId = in.readString();
+ mRoomName = in.readString();
+ mMessageHeader = in.readString();
+ mMessagesSummary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mLatestEventTs = in.readLong();
+ mSenderName = in.readString();
+ mUnreadMessagesCount = in.readInt();
+ }
+
+ public final static Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+
+ public RoomNotifications createFromParcel(Parcel p) {
+ return new RoomNotifications(p);
+ }
+
+ public RoomNotifications[] newArray(int size) {
+ return new RoomNotifications[size];
+ }
+ };
+}
diff --git a/vector/src/main/java/im/vector/notifications/RoomsNotifications.java b/vector/src/main/java/im/vector/notifications/RoomsNotifications.java
new file mode 100755
index 0000000000..d4876f8841
--- /dev/null
+++ b/vector/src/main/java/im/vector/notifications/RoomsNotifications.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.notifications;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.widget.ImageView;
+
+import org.matrix.androidsdk.MXSession;
+import org.matrix.androidsdk.data.Room;
+import org.matrix.androidsdk.data.store.IMXStore;
+import org.matrix.androidsdk.rest.model.Event;
+import org.matrix.androidsdk.rest.model.RoomMember;
+import org.matrix.androidsdk.rest.model.User;
+import org.matrix.androidsdk.util.EventDisplay;
+import org.matrix.androidsdk.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import im.vector.Matrix;
+import im.vector.R;
+import im.vector.VectorApp;
+import im.vector.activity.LockScreenActivity;
+import im.vector.util.RiotEventDisplay;
+import im.vector.util.VectorUtils;
+
+/**
+ * RoomsNotifications
+ */
+public class RoomsNotifications implements Parcelable {
+ private static final String LOG_TAG = RoomsNotifications.class.getSimpleName();
+
+ // max number of lines to display the notification text styles
+ static final int MAX_NUMBER_NOTIFICATION_LINES = 10;
+
+ /****** Parcelable items ********/
+ // the session id
+ String mSessionId = "";
+
+ // the notified event room Id
+ String mRoomId = "";
+
+ // the notification summary
+ String mSummaryText = "";
+
+ // latest message with sender header
+ String mQuickReplyBody = "";
+
+ // wearable notification message
+ String mWearableMessage = "";
+
+ // true when the notified event is an invitation one
+ boolean mIsInvitationEvent = false;
+
+ // the room avatar
+ String mRoomAvatarPath = "";
+
+ // notified message TS
+ long mContentTs = -1;
+
+ // content title
+ String mContentTitle = "";
+
+ // the context text
+ String mContentText = "";
+
+ String mSenderName = "";
+
+ // the notifications list
+ List mRoomNotifications = new ArrayList<>();
+
+ // messages list
+ List mReversedMessagesList = new ArrayList<>();
+
+ /****** others items ********/
+ // notified event
+ private NotifiedEvent mEventToNotify;
+
+ // notified events by room id
+ private Map> mNotifiedEventsByRoomId;
+
+ // notification details
+ private Context mContext;
+ private MXSession mSession;
+ private Room mRoom;
+ private Event mEvent;
+
+ /**
+ * Empty constructor
+ */
+ public RoomsNotifications() {
+ }
+
+ /**
+ * Constructor
+ *
+ * @param anEventToNotify the event to notify
+ * @param someNotifiedEventsByRoomId the notified events
+ */
+ public RoomsNotifications(NotifiedEvent anEventToNotify,
+ Map> someNotifiedEventsByRoomId) {
+ mContext = VectorApp.getInstance();
+ mSession = Matrix.getInstance(mContext).getDefaultSession();
+ IMXStore store = mSession.getDataHandler().getStore();
+
+ mEventToNotify = anEventToNotify;
+ mNotifiedEventsByRoomId = someNotifiedEventsByRoomId;
+
+ // the session id
+ mSessionId = mSession.getMyUserId();
+ mRoomId = anEventToNotify.mRoomId;
+ mRoom = store.getRoom(mEventToNotify.mRoomId);
+ mEvent = store.getEvent(mEventToNotify.mEventId, mEventToNotify.mRoomId);
+
+ // sanity check
+ if ((null == mRoom) || (null == mEvent)) {
+ if (null == mRoom) {
+ Log.e(LOG_TAG, "## RoomsNotifications() : null room " + mEventToNotify.mRoomId);
+ } else {
+ Log.e(LOG_TAG, "## RoomsNotifications() : null event " + mEventToNotify.mEventId + " " + mEventToNotify.mRoomId);
+ }
+ return;
+ }
+
+ mIsInvitationEvent = false;
+
+ EventDisplay eventDisplay = new RiotEventDisplay(mContext, mEvent, mRoom.getLiveState());
+ eventDisplay.setPrependMessagesWithAuthor(true);
+ CharSequence textualDisplay = eventDisplay.getTextualDisplay();
+ String body = !TextUtils.isEmpty(textualDisplay) ? textualDisplay.toString() : "";
+
+ if (Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(mEvent.getType())) {
+ try {
+ mIsInvitationEvent = "invite".equals(mEvent.getContentAsJsonObject().getAsJsonPrimitive("membership").getAsString());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "RoomsNotifications : invitation parsing failed");
+ }
+ }
+ // when the event is an invitation one
+ // don't check if the sender ID is known because the members list are not yet downloaded
+ if (!mIsInvitationEvent) {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size);
+
+ File f = mSession.getMediasCache().thumbnailCacheFile(mRoom.getAvatarUrl(), size);
+
+ if (null != f) {
+ mRoomAvatarPath = f.getPath();
+ } else {
+ // prepare for the next time
+ mSession.getMediasCache().loadAvatarThumbnail(mSession.getHomeServerConfig(), new ImageView(mContext), mRoom.getAvatarUrl(), size);
+ }
+ }
+
+ String roomName = getRoomName(mContext, mSession, mRoom, mEvent);
+
+ mContentTs = mEvent.getOriginServerTs();
+ mContentTitle = roomName;
+ mContentText = body;
+
+ RoomMember member = mRoom.getMember(mEvent.getSender());
+ mSenderName = (null == member) ? mEvent.getSender() : member.getName();
+
+ boolean singleRoom = (mNotifiedEventsByRoomId.size() == 1);
+
+ if (singleRoom) {
+ initSingleRoom();
+ } else {
+ initMultiRooms();
+ }
+ }
+
+ /**
+ * Init for a single room notifications
+ */
+ private void initSingleRoom() {
+ RoomNotifications roomNotifications = new RoomNotifications();
+ mRoomNotifications.add(roomNotifications);
+ roomNotifications.mRoomId = mEvent.roomId;
+ roomNotifications.mRoomName = mContentTitle;
+
+ List notifiedEvents = mNotifiedEventsByRoomId.get(roomNotifications.mRoomId);
+ int unreadCount = notifiedEvents.size();
+
+ // the messages are sorted from the oldest to the latest
+ Collections.reverse(notifiedEvents);
+
+ if (notifiedEvents.size() > MAX_NUMBER_NOTIFICATION_LINES) {
+ notifiedEvents = notifiedEvents.subList(0, MAX_NUMBER_NOTIFICATION_LINES);
+ }
+
+ SpannableString latestText = null;
+ IMXStore store = mSession.getDataHandler().getStore();
+
+ for (NotifiedEvent notifiedEvent : notifiedEvents) {
+ Event event = store.getEvent(notifiedEvent.mEventId, notifiedEvent.mRoomId);
+ EventDisplay eventDisplay = new RiotEventDisplay(mContext, event, mRoom.getLiveState());
+ eventDisplay.setPrependMessagesWithAuthor(true);
+ CharSequence textualDisplay = eventDisplay.getTextualDisplay();
+
+ if (!TextUtils.isEmpty(textualDisplay)) {
+ mReversedMessagesList.add(textualDisplay);
+ }
+ }
+
+ // adapt the notification display to the number of notified messages
+ if ((1 == notifiedEvents.size()) && (null != latestText)) {
+ roomNotifications.mMessagesSummary = latestText;
+ } else {
+ if (unreadCount > MAX_NUMBER_NOTIFICATION_LINES) {
+ mSummaryText = mContext.getString(R.string.notification_unread_notified_messages, unreadCount);
+ }
+ }
+
+ // do not offer to quick respond if the user did not dismiss the previous one
+ if (!LockScreenActivity.isDisplayingALockScreenActivity()) {
+ if (!mIsInvitationEvent) {
+ Event event = store.getEvent(mEventToNotify.mEventId, mEventToNotify.mRoomId);
+ RoomMember member = mRoom.getMember(event.getSender());
+ roomNotifications.mSenderName = (null == member) ? event.getSender() : member.getName();
+
+ EventDisplay eventDisplay = new RiotEventDisplay(mContext, event, mRoom.getLiveState());
+ eventDisplay.setPrependMessagesWithAuthor(false);
+ CharSequence textualDisplay = eventDisplay.getTextualDisplay();
+ mQuickReplyBody = !TextUtils.isEmpty(textualDisplay) ? textualDisplay.toString() : "";
+ }
+ }
+
+ initWearableMessage(mContext, mRoom, store.getEvent(notifiedEvents.get(notifiedEvents.size() - 1).mEventId, roomNotifications.mRoomId), mIsInvitationEvent);
+ }
+
+ /**
+ * Init for multi rooms notifications
+ */
+ private void initMultiRooms() {
+ IMXStore store = mSession.getDataHandler().getStore();
+
+ int sum = 0;
+ int roomsCount = 0;
+
+ for (String roomId : mNotifiedEventsByRoomId.keySet()) {
+ Room room = mSession.getDataHandler().getRoom(roomId);
+ String roomName = getRoomName(mContext, mSession, room, null);
+
+ List notifiedEvents = mNotifiedEventsByRoomId.get(roomId);
+ Event latestEvent = store.getEvent(notifiedEvents.get(notifiedEvents.size() - 1).mEventId, roomId);
+
+ String text;
+ String header;
+
+ EventDisplay eventDisplay = new RiotEventDisplay(mContext, latestEvent, room.getLiveState());
+ eventDisplay.setPrependMessagesWithAuthor(false);
+
+ if (room.isInvited()) {
+ header = roomName + ": ";
+ CharSequence textualDisplay = eventDisplay.getTextualDisplay();
+ text = !TextUtils.isEmpty(textualDisplay) ? textualDisplay.toString() : "";
+ } else if (1 == notifiedEvents.size()) {
+ eventDisplay = new RiotEventDisplay(mContext, latestEvent, room.getLiveState());
+ eventDisplay.setPrependMessagesWithAuthor(false);
+
+ header = roomName + ": " + room.getLiveState().getMemberName(latestEvent.getSender()) + " ";
+
+ CharSequence textualDisplay = eventDisplay.getTextualDisplay();
+
+ // the event might have been redacted
+ if (!TextUtils.isEmpty(textualDisplay)) {
+ text = textualDisplay.toString();
+ } else {
+ text = "";
+ }
+ } else {
+ header = roomName + ": ";
+ text = mContext.getString(R.string.notification_unread_notified_messages, notifiedEvents.size());
+ }
+
+ // ad the line if it makes sense
+ if (!TextUtils.isEmpty(text)) {
+ RoomNotifications roomNotifications = new RoomNotifications();
+ mRoomNotifications.add(roomNotifications);
+
+ roomNotifications.mRoomId = roomId;
+ roomNotifications.mLatestEventTs = latestEvent.getOriginServerTs();
+ roomNotifications.mMessageHeader = header;
+ roomNotifications.mMessagesSummary = header + text;
+ sum += notifiedEvents.size();
+ roomsCount++;
+ }
+ }
+
+ Collections.sort(mRoomNotifications, RoomNotifications.mRoomNotificationsComparator);
+
+ if (mRoomNotifications.size() > MAX_NUMBER_NOTIFICATION_LINES) {
+ mRoomNotifications = mRoomNotifications.subList(0, MAX_NUMBER_NOTIFICATION_LINES);
+ }
+
+ mSummaryText = mContext.getString(R.string.notification_unread_notified_messages_in_room, sum, roomsCount);
+ }
+
+ /**
+ * Compute the wearable message
+ *
+ * @param context the context
+ * @param room the room
+ * @param latestEvent the latest event
+ * @param isInvitationEvent true if it is an invitaion
+ */
+ private void initWearableMessage(Context context, Room room, Event latestEvent, boolean isInvitationEvent) {
+ if (!isInvitationEvent) {
+ // if there is a valid latest message
+ if ((null != latestEvent) && (null != room)) {
+ MXSession session = Matrix.getInstance(context).getDefaultSession();
+ String roomName = getRoomName(context, session, room, null);
+
+ EventDisplay eventDisplay = new RiotEventDisplay(context, latestEvent, room.getLiveState());
+ eventDisplay.setPrependMessagesWithAuthor(false);
+
+ mWearableMessage = roomName + ": " + room.getLiveState().getMemberName(latestEvent.getSender()) + " ";
+ CharSequence textualDisplay = eventDisplay.getTextualDisplay();
+
+ // the event might have been redacted
+ if (!TextUtils.isEmpty(textualDisplay)) {
+ mWearableMessage += textualDisplay.toString();
+ }
+ }
+ }
+ }
+
+ /*
+ * *********************************************************************************************
+ * Parcelable
+ * *********************************************************************************************
+ */
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mSessionId);
+ out.writeString(mRoomId);
+ out.writeString(mSummaryText);
+ out.writeString(mQuickReplyBody);
+ out.writeString(mWearableMessage);
+ out.writeInt(mIsInvitationEvent ? 1 : 0);
+ out.writeString(mRoomAvatarPath);
+ out.writeLong(mContentTs);
+
+ out.writeString(mContentTitle);
+ out.writeString(mContentText);
+ out.writeString(mSenderName);
+
+ RoomNotifications[] roomNotifications = new RoomNotifications[mRoomNotifications.size()];
+ mRoomNotifications.toArray(roomNotifications);
+ out.writeArray(roomNotifications);
+
+ out.writeInt(mReversedMessagesList.size());
+ for (CharSequence sequence : mReversedMessagesList) {
+ TextUtils.writeToParcel(sequence, out, 0);
+ }
+ }
+
+ /**
+ * Constructor from the parcel.
+ *
+ * @param in the parcel
+ */
+ private void init(Parcel in) {
+ mSessionId = in.readString();
+ mRoomId = in.readString();
+ mSummaryText = in.readString();
+ mQuickReplyBody = in.readString();
+ mWearableMessage = in.readString();
+ mIsInvitationEvent = (1 == in.readInt()) ? true : false;
+ mRoomAvatarPath = in.readString();
+ mContentTs = in.readLong();
+
+ mContentTitle = in.readString();
+ mContentText = in.readString();
+ mSenderName = in.readString();
+
+ Object[] roomNotificationsAasVoid = in.readArray(RoomNotifications.class.getClassLoader());
+ for (Object object : roomNotificationsAasVoid) {
+ mRoomNotifications.add((RoomNotifications) object);
+ }
+
+ int count = in.readInt();
+ mReversedMessagesList = new ArrayList<>();
+
+ for (int i = 0; i < count; i++) {
+ mReversedMessagesList.add(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
+ }
+ }
+
+ /**
+ * Parcelable creator
+ */
+ public final static Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public RoomsNotifications createFromParcel(Parcel p) {
+ RoomsNotifications res = new RoomsNotifications();
+ res.init(p);
+ return res;
+ }
+
+ public RoomsNotifications[] newArray(int size) {
+ return new RoomsNotifications[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /*
+ * *********************************************************************************************
+ * Serialisation
+ * *********************************************************************************************
+ */
+
+ private static final String ROOMS_NOTIFICATIONS_FILE_NAME = "ROOMS_NOTIFICATIONS_FILE_NAME";
+
+
+ /**
+ * @return byte[] from the class
+ */
+ private byte[] marshall() {
+ Parcel parcel = Parcel.obtain();
+ writeToParcel(parcel, 0);
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+ return bytes;
+ }
+
+ /**
+ * Create a RoomsNotifications instance from a bytes[].
+ *
+ * @param bytes the bytes array
+ */
+ private RoomsNotifications(byte[] bytes) {
+ Parcel parcel = Parcel.obtain();
+ parcel.unmarshall(bytes, 0, bytes.length);
+ parcel.setDataPosition(0);
+
+ init(parcel);
+ parcel.recycle();
+ }
+
+ /**
+ * Delete the cached RoomNotifications
+ *
+ * @param context the context
+ */
+ public static void deleteCachedRoomNotifications(Context context) {
+ File file = new File(context.getApplicationContext().getCacheDir(), ROOMS_NOTIFICATIONS_FILE_NAME);
+
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ /**
+ * Save the roomsNotifications instance into the file system.
+ *
+ * @param context the context
+ * @param roomsNotifications the roomsNotifications instance
+ */
+ public static void saveRoomNotifications(Context context, RoomsNotifications roomsNotifications) {
+ deleteCachedRoomNotifications(context);
+
+ // no notified messages
+ if (roomsNotifications.mRoomNotifications.isEmpty()) {
+ return;
+ }
+
+ ByteArrayInputStream fis = null;
+ FileOutputStream fos = null;
+
+ try {
+ fis = new ByteArrayInputStream(roomsNotifications.marshall());
+ fos = new FileOutputStream(new File(context.getApplicationContext().getCacheDir(), ROOMS_NOTIFICATIONS_FILE_NAME));
+
+ byte[] readData = new byte[1024];
+ int len;
+
+ while ((len = fis.read(readData, 0, 1024)) > 0) {
+ fos.write(readData, 0, len);
+ }
+ } catch (Throwable t) {
+ Log.e(LOG_TAG, "## saveRoomNotifications() failed " + t.getMessage());
+ }
+
+ try {
+ if (null != fis) {
+ fis.close();
+ }
+
+ if (null != fos) {
+ fos.close();
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## saveRoomNotifications() failed " + e.getMessage());
+ }
+ }
+
+ /**
+ * Load a saved RoomsNotifications from the file system
+ *
+ * @param context the context
+ * @return a RoomsNotifications instance if found
+ */
+ public static RoomsNotifications loadRoomsNotifications(Context context) {
+ File file = new File(context.getApplicationContext().getCacheDir(), ROOMS_NOTIFICATIONS_FILE_NAME);
+
+ // test if the file exits
+ if (!file.exists()) {
+ return null;
+ }
+
+ RoomsNotifications roomsNotifications = null;
+ FileInputStream fis = null;
+ ByteArrayOutputStream fos = null;
+
+ try {
+ fis = new FileInputStream(file);
+ fos = new ByteArrayOutputStream();
+
+
+ byte[] readData = new byte[1024];
+ int len;
+
+ while ((len = fis.read(readData, 0, 1024)) > 0) {
+ fos.write(readData, 0, len);
+ }
+
+ roomsNotifications = new RoomsNotifications(fos.toByteArray());
+ } catch (Throwable t) {
+ Log.e(LOG_TAG, "## loadRoomsNotifications() failed " + t.getMessage());
+ }
+
+ try {
+ if (null != fis) {
+ fis.close();
+ }
+
+ if (null != fos) {
+ fos.close();
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "## loadRoomsNotifications() failed " + e.getMessage());
+ }
+
+ return roomsNotifications;
+ }
+
+ /*
+ * *********************************************************************************************
+ * Utils
+ * *********************************************************************************************
+ */
+
+ /**
+ * Retrieve the room name.
+ *
+ * @param session the session
+ * @param room the room
+ * @param event the event
+ * @return the room name
+ */
+ public static String getRoomName(Context context, MXSession session, Room room, Event event) {
+ String roomName = VectorUtils.getRoomDisplayName(context, session, room);
+
+ // avoid displaying the room Id
+ // try to find the sender display name
+ if (TextUtils.equals(roomName, room.getRoomId())) {
+ roomName = room.getName(session.getMyUserId());
+
+ // avoid room Id as name
+ if (TextUtils.equals(roomName, room.getRoomId()) && (null != event)) {
+ User user = session.getDataHandler().getStore().getUser(event.sender);
+
+ if (null != user) {
+ roomName = user.displayname;
+ } else {
+ roomName = event.sender;
+ }
+ }
+ }
+
+ return roomName;
+ }
+}
diff --git a/vector/src/main/java/im/vector/preference/UserAvatarPreference.java b/vector/src/main/java/im/vector/preference/UserAvatarPreference.java
index dd62d5b63e..8ee4e50ca0 100755
--- a/vector/src/main/java/im/vector/preference/UserAvatarPreference.java
+++ b/vector/src/main/java/im/vector/preference/UserAvatarPreference.java
@@ -17,8 +17,8 @@
package im.vector.preference;
import android.content.Context;
+import android.os.Bundle;
import android.preference.EditTextPreference;
-import android.preference.PreferenceScreen;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -74,4 +74,9 @@ public void setSession(MXSession session) {
mSession = session;
refreshAvatar();
}
+
+ @Override
+ protected void showDialog(Bundle state) {
+ // do nothing
+ }
}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/preference/VectorGroupPreference.java b/vector/src/main/java/im/vector/preference/VectorGroupPreference.java
new file mode 100644
index 0000000000..810956e0bd
--- /dev/null
+++ b/vector/src/main/java/im/vector/preference/VectorGroupPreference.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2016 OpenMarket Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.preference;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import org.matrix.androidsdk.MXSession;
+import org.matrix.androidsdk.data.MyUser;
+import org.matrix.androidsdk.rest.model.group.Group;
+
+import im.vector.R;
+import im.vector.util.VectorUtils;
+
+public class VectorGroupPreference extends VectorSwitchPreference {
+ private static final String LOG_TAG = VectorGroupPreference.class.getSimpleName();
+
+ private Context mContext;
+ private ImageView mAvatarView;
+
+ private Group mGroup;
+ private MXSession mSession;
+
+ /**
+ * Construct a new SwitchPreference with the given style options.
+ *
+ * @param context The Context that will style this preference
+ * @param attrs Style attributes that differ from the default
+ * @param defStyle Theme attribute defining the default style options
+ */
+ public VectorGroupPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * Construct a new SwitchPreference with the given style options.
+ *
+ * @param context The Context that will style this preference
+ * @param attrs Style attributes that differ from the default
+ */
+ public VectorGroupPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public VectorGroupPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context);
+ }
+
+ /**
+ * Construct a new SwitchPreference with default style options.
+ *
+ * @param context The Context that will style this preference
+ */
+ public VectorGroupPreference(Context context) {
+ super(context, null);
+ init(context);
+ }
+
+ @Override
+ protected View onCreateView(ViewGroup parent) {
+ View createdView = super.onCreateView(parent);
+
+ try {
+ // insert the group avatar to the left
+ final ImageView iconView = createdView.findViewById(android.R.id.icon);
+
+ ViewParent iconViewParent = iconView.getParent();
+
+ while (null != iconViewParent.getParent()) {
+ iconViewParent = iconViewParent.getParent();
+ }
+
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ FrameLayout layout = (FrameLayout) inflater.inflate(R.layout.vector_settings_round_group_avatar, null, false);
+ mAvatarView = layout.findViewById(R.id.avatar_img);
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.CENTER;
+ layout.setLayoutParams(params);
+ ((LinearLayout)iconViewParent).addView(layout, 0);
+
+ refreshAvatar();
+ } catch (Exception e) {
+ mAvatarView = null;
+ }
+
+ return createdView;
+ }
+
+ /**
+ * Init the group information
+ *
+ * @param group the group
+ * @param session the session
+ */
+ public void setGroup(Group group, MXSession session) {
+ mGroup = group;
+ mSession = session;
+
+ refreshAvatar();
+ }
+
+ /**
+ * Refresh the avatar
+ */
+ public void refreshAvatar() {
+ if ((null != mAvatarView) && (null != mSession) && (null != mGroup)) {
+ VectorUtils.loadGroupAvatar(mContext, mSession, mAvatarView ,mGroup);
+ }
+ }
+
+ /**
+ * Common init method.
+ *
+ * @param context the context
+ */
+ private void init(Context context) {
+ // Force the use of SwitchCompat component
+ setWidgetLayoutResource(R.layout.preference_switch_layout);
+ mContext = context;
+ }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/receiver/VectorUniversalLinkReceiver.java b/vector/src/main/java/im/vector/receiver/VectorUniversalLinkReceiver.java
index 3ac9972fa6..2ebb32c112 100644
--- a/vector/src/main/java/im/vector/receiver/VectorUniversalLinkReceiver.java
+++ b/vector/src/main/java/im/vector/receiver/VectorUniversalLinkReceiver.java
@@ -46,6 +46,7 @@
import im.vector.VectorApp;
import im.vector.activity.CommonActivityUtils;
import im.vector.activity.LoginActivity;
+import im.vector.activity.VectorGroupDetailsActivity;
import im.vector.activity.VectorHomeActivity;
import im.vector.activity.VectorMemberDetailsActivity;
import im.vector.activity.VectorRoomActivity;
@@ -81,6 +82,7 @@ public class VectorUniversalLinkReceiver extends BroadcastReceiver {
// index of each item in path
public static final String ULINK_ROOM_ID_OR_ALIAS_KEY = "ULINK_ROOM_ID_OR_ALIAS_KEY";
public static final String ULINK_MATRIX_USER_ID_KEY = "ULINK_MATRIX_USER_ID_KEY";
+ public static final String ULINK_GROUP_ID_KEY = "ULINK_GROUP_ID_KEY";
private static final String ULINK_EVENT_ID_KEY = "ULINK_EVENT_ID_KEY";
/*public static final String ULINK_EMAIL_ID_KEY = "email";
public static final String ULINK_SIGN_URL_KEY = "signurl";
@@ -181,6 +183,8 @@ public void onReceive(final Context aContext, final Intent aIntent) {
manageRoomOnActivity(aContext);
} else if (mParameters.containsKey(ULINK_MATRIX_USER_ID_KEY)) {
manageMemberDetailsActivity(aContext);
+ } else if (mParameters.containsKey(ULINK_GROUP_ID_KEY)) {
+ manageGroupDetailsActivity(aContext);
} else {
Log.e(LOG_TAG, "## onReceive() : nothing to do");
}
@@ -255,6 +259,32 @@ public void run() {
}
}
+ /**
+ * Start the universal link management when the login process is done.
+ * If there is no active activity, launch the home activity
+ *
+ * @param aContext the context.
+ */
+ private void manageGroupDetailsActivity(final Context aContext) {
+ Log.d(LOG_TAG, "## manageMemberDetailsActivity() : open the group" + mParameters.get(ULINK_GROUP_ID_KEY));
+
+ final Activity currentActivity = VectorApp.getCurrentActivity();
+
+ if (null != currentActivity) {
+ Intent startRoomInfoIntent = new Intent(currentActivity, VectorGroupDetailsActivity.class);
+ startRoomInfoIntent.putExtra(VectorGroupDetailsActivity.EXTRA_GROUP_ID, mParameters.get(ULINK_GROUP_ID_KEY));
+ startRoomInfoIntent.putExtra(VectorGroupDetailsActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
+ currentActivity.startActivity(startRoomInfoIntent);
+ } else {
+ // clear the activity stack to home activity
+ Intent intent = new Intent(aContext, VectorHomeActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(VectorHomeActivity.EXTRA_GROUP_ID, mParameters.get(ULINK_GROUP_ID_KEY));
+ aContext.startActivity(intent);
+ }
+ }
+
+
/**
* Manage the room presence.
* Check the URL room ID format: if room ID is provided as an alias, we translate it
@@ -430,6 +460,8 @@ public static HashMap parseUniversalLink(Uri uri) {
map.put(ULINK_MATRIX_USER_ID_KEY, firstParam);
} else if (MXSession.isRoomAlias(firstParam) || MXSession.isRoomId(firstParam)) {
map.put(ULINK_ROOM_ID_OR_ALIAS_KEY, firstParam);
+ } else if (MXSession.isGroupId(firstParam)) {
+ map.put(ULINK_GROUP_ID_KEY, firstParam);
}
// room id only ?
diff --git a/vector/src/main/java/im/vector/services/EventStreamService.java b/vector/src/main/java/im/vector/services/EventStreamService.java
index cc7862397a..edc37f231e 100755
--- a/vector/src/main/java/im/vector/services/EventStreamService.java
+++ b/vector/src/main/java/im/vector/services/EventStreamService.java
@@ -70,9 +70,12 @@
import im.vector.ViewedRoomTracker;
import im.vector.activity.VectorHomeActivity;
import im.vector.gcm.GcmRegistrationManager;
+import im.vector.notifications.NotificationUtils;
+import im.vector.notifications.NotifiedEvent;
+import im.vector.notifications.RoomsNotifications;
import im.vector.receiver.DismissNotificationReceiver;
import im.vector.util.CallsManager;
-import im.vector.util.NotificationUtils;
+import im.vector.util.PreferencesManager;
import im.vector.util.RiotEventDisplay;
/**
@@ -105,19 +108,30 @@ public enum StreamAction {
*/
public static final String EXTRA_STREAM_ACTION = "EventStreamService.EXTRA_STREAM_ACTION";
public static final String EXTRA_MATRIX_IDS = "EventStreamService.EXTRA_MATRIX_IDS";
- private static final String EXTRA_AUTO_RESTART_ACTION = "EventStreamService.EXTRA_AUTO_RESTART_ACTION";
+ public static final String EXTRA_AUTO_RESTART_ACTION = "EventStreamService.EXTRA_AUTO_RESTART_ACTION";
/**
* Notification identifiers
*/
- private static final int NOTIF_ID_MESSAGE = 60;
- private static final int NOTIF_ID_FOREGROUND_SERVICE = 61;
+ private static final int NOTIFICATION_ID = 123;
+
+ public enum NotificationState {
+ // no notifications are displayed
+ NONE,
+ // initial sync in progress
+ INITIAL_SYNCING,
+ // fdroid mode or GCM registration failed
+ // put this service in foreground to keep it in life
+ LISTENING_FOR_EVENTS,
+ // display events notifications
+ DISPLAYING_EVENTS_NOTIFICATIONS,
+ // there is a pending incoming call
+ INCOMING_CALL,
+ // a call is in progress
+ CALL_IN_PROGRESS,
+ }
- private static final int FOREGROUND_INITIAL_SYNCING = 41;
- private static final int FOREGROUND_LISTENING_FOR_EVENTS = 42;
- private static final int FOREGROUND_NOTIF_ID_PENDING_CALL = 44;
- private static final int FOREGROUND_ID_INCOMING_CALL = 45;
- private int mForegroundServiceIdentifier = -1;
+ private static NotificationState mNotificationState = NotificationState.NONE;
/**
* Default bing rule
@@ -142,8 +156,8 @@ public enum StreamAction {
/**
* store the notifications description
*/
- private final LinkedHashMap mPendingNotifications = new LinkedHashMap<>();
- private Map> mNotifiedEventsByRoomId = null;
+ private final LinkedHashMap mPendingNotifications = new LinkedHashMap<>();
+ private Map> mNotifiedEventsByRoomId = null;
private static HandlerThread mNotificationHandlerThread = null;
private static android.os.Handler mNotificationsHandler = null;
@@ -426,7 +440,7 @@ private void autoRestart() {
Log.d(LOG_TAG, "## autoRestart() : restarts after " + delay + " ms");
// reset the service identifier
- mForegroundServiceIdentifier = -1;
+ mNotificationState = NotificationState.NONE;
// restart the services after 3 seconds
Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
@@ -457,11 +471,23 @@ public void onTaskRemoved(Intent rootIntent) {
@Override
public void onDestroy() {
if (!mIsSelfDestroyed) {
- Log.d(LOG_TAG, "## onDestroy() : restart it");
setServiceState(StreamAction.STOP);
+
+ // stop the foreground service on devices which uses the battery optimisation
+ // during the initial syncing
+ // and if the GCM registration was done
+ if (PreferencesManager.useBatteryOptimisation(getApplicationContext()) &&
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) &&
+ (mNotificationState == NotificationState.INITIAL_SYNCING)
+ && Matrix.getInstance(getApplicationContext()).getSharedGCMRegistrationManager().hasRegistrationToken()) {
+ stopForeground(true);
+ mIsForeground = false;
+ }
+
+ Log.d(LOG_TAG, "## onDestroy() : restart it");
autoRestart();
} else {
- Log.d(LOG_TAG, "## onDestroy()");
+ Log.d(LOG_TAG, "## onDestroy() : do nothing");
stop();
super.onDestroy();
}
@@ -536,7 +562,7 @@ public void run() {
(new Handler(getMainLooper())).post(new Runnable() {
@Override
public void run() {
- updateServiceForegroundState();
+ refreshStatusNotification();
}
});
}
@@ -647,13 +673,20 @@ private void start() {
monitorSession(session);
}
- if (!mGcmRegistrationManager.useGCM()) {
- updateServiceForegroundState();
- }
+ refreshStatusNotification();
setServiceState(StreamAction.START);
}
+ /**
+ * Stop the service without delay
+ */
+ public void stopNow() {
+ stop();
+ mIsSelfDestroyed = true;
+ stopSelf();
+ }
+
/**
* internal stop.
*/
@@ -761,24 +794,33 @@ private void gcmStatusUpdate() {
Log.d(LOG_TAG, "## gcmStatusUpdate");
if (mIsForeground) {
- Log.d(LOG_TAG, "## gcmStatusUpdate : gcm status succeeds so stopForeground");
- if (FOREGROUND_LISTENING_FOR_EVENTS == mForegroundServiceIdentifier) {
+ Log.d(LOG_TAG, "## gcmStatusUpdate : gcm status succeeds so stopForeground (" + mNotificationState + ")");
+
+ if (NotificationState.LISTENING_FOR_EVENTS == mNotificationState) {
stopForeground(true);
- mForegroundServiceIdentifier = -1;
+ mNotificationState = NotificationState.NONE;
mIsForeground = false;
}
}
- updateServiceForegroundState();
+ refreshStatusNotification();
}
/**
- * Enable/disable the service foreground status.
- * The service is put in foreground ("Foreground process priority") when a sync polling is used,
- * to strongly reduce the likelihood of the App being killed.
+ * @return true if the "listen for events" notification should be displayed
*/
- private void updateServiceForegroundState() {
- Log.d(LOG_TAG, "## updateServiceForegroundState");
+ private boolean shouldDisplayListenForEventsNotification() {
+ // fdroid
+ return (!mGcmRegistrationManager.useGCM() ||
+ // the GCM registration was not done
+ TextUtils.isEmpty(mGcmRegistrationManager.getCurrentRegistrationToken()) && !mGcmRegistrationManager.isServerRegistred()) && mGcmRegistrationManager.isBackgroundSyncAllowed() && mGcmRegistrationManager.areDeviceNotificationsAllowed();
+ }
+
+ /**
+ * Manages the "listen for events" and "synchronising" notifications
+ */
+ public void refreshStatusNotification() {
+ Log.d(LOG_TAG, "## refreshStatusNotification from state " + mNotificationState);
MXSession session = Matrix.getInstance(getApplicationContext()).getDefaultSession();
@@ -787,43 +829,70 @@ private void updateServiceForegroundState() {
return;
}
+ // call in progress notifications
+ if ((mNotificationState == NotificationState.INCOMING_CALL) || (mNotificationState == NotificationState.CALL_IN_PROGRESS)) {
+ Log.d(LOG_TAG, "## refreshStatusNotification : does nothing as there is a pending call");
+ return;
+ }
+
+ if (mNotificationState == NotificationState.DISPLAYING_EVENTS_NOTIFICATIONS) {
+ if (PreferencesManager.useBatteryOptimisation(getApplicationContext()) &&
+ ((mServiceState == StreamAction.CATCHUP) || isStopped()) && !mIsForeground) {
+ if (mServiceState == StreamAction.CATCHUP) {
+ Log.d(LOG_TAG, "## refreshStatusNotification : events notif is displayed but the application is catchup up");
+ } else {
+ Log.d(LOG_TAG, "## refreshStatusNotification : events notif is displayed but the service was stopped");
+ }
+ mNotificationState = NotificationState.NONE;
+ } else {
+ Log.d(LOG_TAG, "## refreshStatusNotification : displaying events notification");
+ }
+ return;
+ }
+
+ if (mNotificationState == NotificationState.NONE) {
+ Notification notification = NotificationUtils.buildMessageNotification(getApplicationContext(), true);
+
+ if (null != notification) {
+ mNotificationState = NotificationState.DISPLAYING_EVENTS_NOTIFICATIONS;
+ startForeground(NOTIFICATION_ID, notification);
+ mIsForeground = true;
+ Log.d(LOG_TAG, "## refreshStatusNotification : restore the events notification");
+ return;
+ }
+ }
+
// GA issue
if (null == mGcmRegistrationManager) {
return;
}
- boolean isInitialSyncInProgress = !session.getDataHandler().isInitialSyncComplete();
+ boolean isInitialSyncInProgress = !session.getDataHandler().isInitialSyncComplete() || isStopped() || (mServiceState == StreamAction.CATCHUP);
if (isInitialSyncInProgress) {
- Log.d(LOG_TAG, "## updateServiceForegroundState : put the service in foreground because of an initial sync");
+ Log.d(LOG_TAG, "## refreshStatusNotification : put the service in foreground because of an initial sync " + mNotificationState);
- if (FOREGROUND_INITIAL_SYNCING != mForegroundServiceIdentifier) {
- Notification notification = buildForegroundServiceNotification(getString(R.string.notification_sync_in_progress));
- startForeground(NOTIF_ID_FOREGROUND_SERVICE, notification);
- mForegroundServiceIdentifier = FOREGROUND_INITIAL_SYNCING;
+ if (mNotificationState != NotificationState.INITIAL_SYNCING) {
+ startForeground(NOTIFICATION_ID, buildForegroundServiceNotification(getString(R.string.notification_sync_in_progress)));
+ mNotificationState = NotificationState.INITIAL_SYNCING;
}
-
mIsForeground = true;
- } else if (
- // fdroid
- (!mGcmRegistrationManager.useGCM() ||
- // the GCM registration was not done
- TextUtils.isEmpty(mGcmRegistrationManager.getCurrentRegistrationToken()) && !mGcmRegistrationManager.isServerRegistred()) && mGcmRegistrationManager.isBackgroundSyncAllowed() && mGcmRegistrationManager.areDeviceNotificationsAllowed()) {
- Log.d(LOG_TAG, "## updateServiceForegroundState : put the service in foreground because of GCM registration");
-
- if (FOREGROUND_LISTENING_FOR_EVENTS != mForegroundServiceIdentifier) {
- Notification notification = buildForegroundServiceNotification(getString(R.string.notification_listen_for_events));
- startForeground(NOTIF_ID_FOREGROUND_SERVICE, notification);
- mForegroundServiceIdentifier = FOREGROUND_LISTENING_FOR_EVENTS;
+ } else if (shouldDisplayListenForEventsNotification()) {
+ Log.d(LOG_TAG, "## refreshStatusNotification : put the service in foreground because of GCM registration");
+
+ if (mNotificationState != NotificationState.LISTENING_FOR_EVENTS) {
+ startForeground(NOTIFICATION_ID, buildForegroundServiceNotification(getString(R.string.notification_listen_for_events)));
+ mNotificationState = NotificationState.LISTENING_FOR_EVENTS;
}
mIsForeground = true;
} else {
- Log.d(LOG_TAG, "## updateServiceForegroundState : put the service in background");
-
- if ((FOREGROUND_LISTENING_FOR_EVENTS == mForegroundServiceIdentifier) || (FOREGROUND_INITIAL_SYNCING == mForegroundServiceIdentifier)) {
+ if ((mNotificationState == NotificationState.LISTENING_FOR_EVENTS) || (mNotificationState == NotificationState.INITIAL_SYNCING)) {
+ Log.d(LOG_TAG, "## refreshStatusNotification : put the service in background from state " + mNotificationState);
stopForeground(true);
- mForegroundServiceIdentifier = -1;
+ mNotificationState = NotificationState.NONE;
+ } else {
+ Log.d(LOG_TAG, "## refreshStatusNotification : nothing to do");
}
mIsForeground = false;
}
@@ -987,7 +1056,7 @@ private void prepareNotification(Event event, BingRule bingRule) {
bingRule = mDefaultBingRule;
}
- mPendingNotifications.put(event.eventId, new NotificationUtils.NotifiedEvent(event.roomId, event.eventId, bingRule, event.getOriginServerTs()));
+ mPendingNotifications.put(event.eventId, new NotifiedEvent(event.roomId, event.eventId, bingRule, event.getOriginServerTs()));
}
/**
@@ -1070,6 +1139,8 @@ public void run() {
if (null != mNotifiedEventsByRoomId) {
mNotifiedEventsByRoomId.clear();
}
+
+ RoomsNotifications.deleteCachedRoomNotifications(VectorApp.getInstance());
}
});
}
@@ -1132,18 +1203,27 @@ public static void onStaticNotifiedEvent(Context context, Event event, String ro
if ((null != event) && !mBackgroundNotificationEventIds.contains(event.eventId)) {
mBackgroundNotificationEventIds.add(event.eventId);
-
- String header = (TextUtils.isEmpty(roomName) ? event.roomId : roomName) + ": " +
- (TextUtils.isEmpty(senderDisplayName) ? event.sender : senderDisplayName) + " ";
-
+ String header = "";
String text;
- if (event.isEncrypted()) {
- text = context.getString(R.string.encrypted_message);
+ if (null == event.content) {
+ if (1 == mBackgroundNotificationEventIds.size()) {
+ text = context.getString(R.string.one_new_message);
+ } else {
+ text = context.getString(R.string.new_messages, mBackgroundNotificationEventIds.size());
+ }
+ mBackgroundNotificationStrings.clear();
} else {
- EventDisplay eventDisplay = new RiotEventDisplay(context, event, null);
- eventDisplay.setPrependMessagesWithAuthor(false);
- text = eventDisplay.getTextualDisplay().toString();
+ header = (TextUtils.isEmpty(roomName) ? event.roomId : roomName) + ": " +
+ (TextUtils.isEmpty(senderDisplayName) ? event.sender : senderDisplayName) + " ";
+
+ if (event.isEncrypted()) {
+ text = context.getString(R.string.encrypted_message);
+ } else {
+ EventDisplay eventDisplay = new RiotEventDisplay(context, event, null);
+ eventDisplay.setPrependMessagesWithAuthor(false);
+ text = eventDisplay.getTextualDisplay().toString();
+ }
}
if (!TextUtils.isEmpty(text)) {
@@ -1154,117 +1234,38 @@ public static void onStaticNotifiedEvent(Context context, Event event, String ro
Notification notification = NotificationUtils.buildMessagesListNotification(context, mBackgroundNotificationStrings, new BingRule(null, null, true, true, true));
if (null != notification) {
- nm.notify(NOTIF_ID_MESSAGE, notification);
+ nm.notify(NOTIFICATION_ID, notification);
+ mNotificationState = NotificationState.DISPLAYING_EVENTS_NOTIFICATIONS;
} else {
- nm.cancel(NOTIF_ID_MESSAGE);
+ nm.cancel(NOTIFICATION_ID);
+ mNotificationState = NotificationState.NONE;
}
}
} else if (0 == unreadMessagesCount) {
mBackgroundNotificationStrings.clear();
- nm.cancel(NOTIF_ID_MESSAGE);
+ nm.cancel(NOTIFICATION_ID);
+ mNotificationState = NotificationState.NONE;
}
}
/**
- * Notify that a notification for even has been received.
+ * Dismiss the messages notifications.
*
- * @param event the notified event
- * @param roomName the room name
- * @param senderDisplayName the sender display name
- * @param unreadMessagesCount the unread messages count
+ * @param nm the notifications manager
*/
- public void onNotifiedEventWithBackgroundSyncDisabled(Event event, String roomName, String senderDisplayName, int unreadMessagesCount) {
- if ((null != event) && !mBackgroundNotificationEventIds.contains(event.eventId)) {
- mBackgroundNotificationEventIds.add(event.eventId);
-
- // TODO the session id should be provided by the server
- MXSession session = Matrix.getInstance(getApplicationContext()).getDefaultSession();
-
- if (null != session) {
- RoomState roomState = null;
-
- try {
- roomState = session.getDataHandler().getRoom(event.roomId).getLiveState();
- } catch (Exception e) {
- Log.e(LOG_TAG, "Fail to retrieve the roomState of " + event.roomId);
- }
-
- if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED) && session.isCryptoEnabled()) {
- session.getDataHandler().decryptEvent(event, null);
- }
-
- // test if the message is displayable
- EventDisplay eventDisplay = new RiotEventDisplay(getApplicationContext(), event, roomState);
- eventDisplay.setPrependMessagesWithAuthor(false);
- String text = eventDisplay.getTextualDisplay().toString();
-
- // display a dedicated message in decryption error cases
- if (null != event.getCryptoError()) {
- text = getApplicationContext().getString(R.string.encrypted_message);
- }
-
- // sanity check
- if (!TextUtils.isEmpty(text) && (null != roomState)) {
-
- if (TextUtils.isEmpty(roomName)) {
- roomName = roomState.getDisplayName(session.getMyUserId());
- }
-
- if (TextUtils.isEmpty(senderDisplayName)) {
- senderDisplayName = roomState.getMemberName(event.sender);
- }
-
- String header = roomName + ": " + senderDisplayName + " ";
-
- SpannableString notifiedLine = new SpannableString(header + text);
- notifiedLine.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, header.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- Log.d(LOG_TAG, "## onMessageReceivedInternal() : trigger a notification " + notifiedLine);
-
- mBackgroundNotificationStrings.add(0, notifiedLine);
- BingRulesManager bingRulesManager = session.getDataHandler().getBingRulesManager();
- BingRule rule = bingRulesManager.isReady() ? bingRulesManager.fulfilledBingRule(event) : new BingRule(false);
-
- displayMessagesNotification(mBackgroundNotificationStrings, rule);
- }
+ private void dismissMessagesNotification(NotificationManagerCompat nm) {
+ if (mNotificationState == NotificationState.DISPLAYING_EVENTS_NOTIFICATIONS) {
+ Log.d(LOG_TAG, "## dismissMessagesNotification() : clear notification");
+ if (mIsForeground) {
+ stopForeground(true);
+ mIsForeground = false;
+ } else {
+ nm.cancel(NOTIFICATION_ID);
}
- } else if (0 == unreadMessagesCount) {
- mBackgroundNotificationStrings.clear();
- displayMessagesNotification(mBackgroundNotificationStrings, null);
- }
- }
-
- /**
- * Display a list of events as string.
- *
- * @param messages the messages list
- * @param rule the bing rule to use
- */
- private void displayMessagesNotification(final List messages, final BingRule rule) {
- NotificationUtils.addNotificationChannels(this);
- final NotificationManagerCompat nm = NotificationManagerCompat.from(EventStreamService.this);
-
- if (!mGcmRegistrationManager.areDeviceNotificationsAllowed() || (null == messages) || (0 == messages.size())) {
- new Handler(getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- nm.cancel(NOTIF_ID_MESSAGE);
- }
- });
- } else {
- new Handler(getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- Notification notif = NotificationUtils.buildMessagesListNotification(getApplicationContext(), messages, rule);
- if (null != notif) {
- nm.notify(NOTIF_ID_MESSAGE, notif);
-
- } else {
- nm.cancel(NOTIF_ID_MESSAGE);
- }
- }
- });
+ mNotificationState = NotificationState.NONE;
+ RoomsNotifications.deleteCachedRoomNotifications(getApplicationContext());
+ refreshStatusNotification();
}
}
@@ -1279,13 +1280,13 @@ private void refreshMessagesNotification() {
final NotificationManagerCompat nm = NotificationManagerCompat.from(EventStreamService.this);
- NotificationUtils.NotifiedEvent eventToNotify = getEventToNotify();
+ NotifiedEvent eventToNotify = getEventToNotify();
if (!mGcmRegistrationManager.areDeviceNotificationsAllowed()) {
mNotifiedEventsByRoomId = null;
new Handler(getMainLooper()).post(new Runnable() {
@Override
public void run() {
- nm.cancel(NOTIF_ID_MESSAGE);
+ dismissMessagesNotification(nm);
}
});
} else if (refreshNotifiedMessagesList()) {
@@ -1294,7 +1295,7 @@ public void run() {
new Handler(getMainLooper()).post(new Runnable() {
@Override
public void run() {
- nm.cancel(NOTIF_ID_MESSAGE);
+ dismissMessagesNotification(nm);
}
});
} else {
@@ -1316,8 +1317,8 @@ public void run() {
// search the latest message to refresh the notification
for (String roomId : roomIds) {
- List events = mNotifiedEventsByRoomId.get(roomId);
- NotificationUtils.NotifiedEvent notifiedEvent = events.get(events.size() - 1);
+ List events = mNotifiedEventsByRoomId.get(roomId);
+ NotifiedEvent notifiedEvent = events.get(events.size() - 1);
Event event = store.getEvent(notifiedEvent.mEventId, notifiedEvent.mRoomId);
@@ -1332,8 +1333,8 @@ public void run() {
}
}
- final NotificationUtils.NotifiedEvent fEventToNotify = eventToNotify;
- final Map> fNotifiedEventsByRoomId = new HashMap<>(mNotifiedEventsByRoomId);
+ final NotifiedEvent fEventToNotify = eventToNotify;
+ final Map> fNotifiedEventsByRoomId = new HashMap<>(mNotifiedEventsByRoomId);
if (null != fEventToNotify) {
DismissNotificationReceiver.setLatestNotifiedMessageTs(this, fEventToNotify.mOriginServerTs);
@@ -1351,13 +1352,25 @@ public void run() {
// the notification cannot be built
if (null != notif) {
- nm.notify(NOTIF_ID_MESSAGE, notif);
+ if (shouldDisplayListenForEventsNotification()) {
+ mIsForeground = true;
+ startForeground(NOTIFICATION_ID, notif);
+ } else {
+ if (mIsForeground) {
+ stopForeground(true);
+ mIsForeground = false;
+ }
+ nm.notify(NOTIFICATION_ID, notif);
+ }
+ mNotificationState = NotificationState.DISPLAYING_EVENTS_NOTIFICATIONS;
+ Log.d(LOG_TAG, "## refreshMessagesNotification() : display the notification");
} else {
- nm.cancel(NOTIF_ID_MESSAGE);
+ Log.d(LOG_TAG, "## refreshMessagesNotification() : nothing to display");
+ dismissMessagesNotification(nm);
}
} else {
Log.e(LOG_TAG, "## refreshMessagesNotification() : mNotifiedEventsByRoomId is empty");
- nm.cancel(NOTIF_ID_MESSAGE);
+ dismissMessagesNotification(nm);
}
}
});
@@ -1369,18 +1382,18 @@ public void run() {
* Check if the current displayed notification must be cleared
* because it doesn't make sense anymore.
*/
- private NotificationUtils.NotifiedEvent getEventToNotify() {
+ private NotifiedEvent getEventToNotify() {
if (mPendingNotifications.size() > 0) {
// TODO add multi sessions
MXSession session = Matrix.getInstance(getBaseContext()).getDefaultSession();
IMXStore store = session.getDataHandler().getStore();
// notified only the latest unread message
- List eventsToNotify = new ArrayList<>(mPendingNotifications.values());
+ List eventsToNotify = new ArrayList<>(mPendingNotifications.values());
Collections.reverse(eventsToNotify);
- for (NotificationUtils.NotifiedEvent eventToNotify : eventsToNotify) {
+ for (NotifiedEvent eventToNotify : eventsToNotify) {
Room room = store.getRoom(eventToNotify.mRoomId);
// test if the message has not been read
@@ -1467,8 +1480,8 @@ private boolean refreshNotifiedMessagesList() {
BingRule rule = session.fulfillRule(event);
if ((null != rule) && rule.isEnabled && rule.shouldNotify()) {
- List list = new ArrayList<>();
- list.add(new NotificationUtils.NotifiedEvent(event.roomId, event.eventId, rule, event.getOriginServerTs()));
+ List list = new ArrayList<>();
+ list.add(new NotifiedEvent(event.roomId, event.eventId, rule, event.getOriginServerTs()));
mNotifiedEventsByRoomId.put(room.getRoomId(), list);
}
}
@@ -1483,14 +1496,14 @@ private boolean refreshNotifiedMessagesList() {
List unreadEvents = store.unreadEvents(room.getRoomId(), null);
if ((null != unreadEvents) && unreadEvents.size() > 0) {
- List list = new ArrayList<>();
+ List list = new ArrayList<>();
for (Event event : unreadEvents) {
if (event.getOriginServerTs() > minTs) {
BingRule rule = session.fulfillRule(event);
if ((null != rule) && rule.isEnabled && rule.shouldNotify()) {
- list.add(new NotificationUtils.NotifiedEvent(event.roomId, event.eventId, rule, event.getOriginServerTs()));
+ list.add(new NotifiedEvent(event.roomId, event.eventId, rule, event.getOriginServerTs()));
//Log.d(LOG_TAG, "## refreshNotifiedMessagesList() : the event " + event.eventId + " in room " + event.roomId + " fulfills " + rule);
}
} else {
@@ -1525,20 +1538,20 @@ private boolean refreshNotifiedMessagesList() {
isUpdated = true;
} else {
// the messages are sorted from the oldest to the latest
- List events = mNotifiedEventsByRoomId.get(roomId);
+ List events = mNotifiedEventsByRoomId.get(roomId);
// if the oldest event has been read
// something has been updated
- NotificationUtils.NotifiedEvent oldestEvent = events.get(0);
+ NotifiedEvent oldestEvent = events.get(0);
if (room.isEventRead(oldestEvent.mEventId) || (oldestEvent.mOriginServerTs < minTs)) {
// if the latest message has been read
// we have to find out the unread messages
- NotificationUtils.NotifiedEvent latestEvent = events.get(events.size() - 1);
+ NotifiedEvent latestEvent = events.get(events.size() - 1);
if (!room.isEventRead(latestEvent.mEventId) && latestEvent.mOriginServerTs > minTs) {
// search for the read messages
for (int i = 0; i < events.size(); ) {
- NotificationUtils.NotifiedEvent event = events.get(i);
+ NotifiedEvent event = events.get(i);
if (room.isEventRead(event.mEventId) || (event.mOriginServerTs <= minTs)) {
// Log.d(LOG_TAG, "## refreshNotifiedMessagesList() : the event " + event.mEventId + " in room " + room.getRoomId() + " is read");
@@ -1597,12 +1610,14 @@ else if (null == CallsManager.getSharedInstance().getActiveCall()) {
Log.d(LOG_TAG, "displayIncomingCallNotification : display the dedicated notification");
Notification notification = NotificationUtils.buildIncomingCallNotification(
EventStreamService.this,
- NotificationUtils.getRoomName(getApplicationContext(), session, room, event),
+ RoomsNotifications.getRoomName(getApplicationContext(), session, room, event),
session.getMyUserId(),
callId);
- startForeground(NOTIF_ID_FOREGROUND_SERVICE, notification);
- mForegroundServiceIdentifier = FOREGROUND_ID_INCOMING_CALL;
+
+ startForeground(NOTIFICATION_ID, notification);
+ mNotificationState = NotificationState.INCOMING_CALL;
+ mIsForeground = true;
mIncomingCallId = callId;
@@ -1619,7 +1634,7 @@ else if (null == CallsManager.getSharedInstance().getActiveCall()) {
}
/**
- * Display a call in progress notificatin.
+ * Display a call in progress notification.
*
* @param session the session
* @param callId the callId
@@ -1627,8 +1642,8 @@ else if (null == CallsManager.getSharedInstance().getActiveCall()) {
public void displayCallInProgressNotification(MXSession session, Room room, String callId) {
if (null != callId) {
Notification notification = NotificationUtils.buildPendingCallNotification(getApplicationContext(), room.getName(session.getCredentials().userId), room.getRoomId(), session.getCredentials().userId, callId);
- startForeground(NOTIF_ID_FOREGROUND_SERVICE, notification);
- mForegroundServiceIdentifier = FOREGROUND_NOTIF_ID_PENDING_CALL;
+ startForeground(NOTIFICATION_ID, notification);
+ mNotificationState = NotificationState.CALL_IN_PROGRESS;
mCallIdInProgress = callId;
}
}
@@ -1640,16 +1655,17 @@ public void hideCallNotifications() {
NotificationManager nm = (NotificationManager) EventStreamService.this.getSystemService(Context.NOTIFICATION_SERVICE);
// hide the call
- if ((FOREGROUND_NOTIF_ID_PENDING_CALL == mForegroundServiceIdentifier) || (FOREGROUND_ID_INCOMING_CALL == mForegroundServiceIdentifier)) {
- if (FOREGROUND_NOTIF_ID_PENDING_CALL == mForegroundServiceIdentifier) {
+ if ((NotificationState.CALL_IN_PROGRESS == mNotificationState) || (NotificationState.INCOMING_CALL == mNotificationState)) {
+ if (NotificationState.CALL_IN_PROGRESS == mNotificationState) {
mCallIdInProgress = null;
} else {
mIncomingCallId = null;
}
- nm.cancel(NOTIF_ID_FOREGROUND_SERVICE);
- mForegroundServiceIdentifier = -1;
+ nm.cancel(NOTIFICATION_ID);
stopForeground(true);
- updateServiceForegroundState();
+
+ mNotificationState = NotificationState.NONE;
+ refreshStatusNotification();
}
}
}
diff --git a/vector/src/main/java/im/vector/util/BugReporter.java b/vector/src/main/java/im/vector/util/BugReporter.java
index c4bedafb48..09c7f6a3fc 100755
--- a/vector/src/main/java/im/vector/util/BugReporter.java
+++ b/vector/src/main/java/im/vector/util/BugReporter.java
@@ -300,6 +300,7 @@ public void onWrite(long totalWritten, long contentLength) {
int responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
Response response = null;
+ String errorMessage = null;
// trigger the request
try {
@@ -308,11 +309,14 @@ public void onWrite(long totalWritten, long contentLength) {
responseCode = response.code();
} catch (Exception e) {
Log.e(LOG_TAG, "response " + e.getMessage());
+ errorMessage = e.getLocalizedMessage();
}
// if the upload failed, try to retrieve the reason
if (responseCode != HttpURLConnection.HTTP_OK) {
- if ((null == response) || (null == response.body())) {
+ if (null != errorMessage) {
+ serverError = "Failed with error " + errorMessage;
+ } else if ((null == response) || (null == response.body())) {
serverError = "Failed with error " + responseCode;
} else {
InputStream is = null;
diff --git a/vector/src/main/java/im/vector/util/CallsManager.java b/vector/src/main/java/im/vector/util/CallsManager.java
index 6927f5e812..7bc405954f 100755
--- a/vector/src/main/java/im/vector/util/CallsManager.java
+++ b/vector/src/main/java/im/vector/util/CallsManager.java
@@ -22,6 +22,7 @@
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.Toast;
@@ -356,9 +357,22 @@ public void onIncomingCall(final IMXCall aCall, final MXUsersDevicesMap launch it");
- Context context = VectorApp.getInstance();
-
// clear the activity stack to home activity
Intent intent = new Intent(context, VectorHomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/vector/src/main/java/im/vector/util/GroupUtils.java b/vector/src/main/java/im/vector/util/GroupUtils.java
new file mode 100644
index 0000000000..0adc83608c
--- /dev/null
+++ b/vector/src/main/java/im/vector/util/GroupUtils.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.util;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.text.TextUtils;
+
+import org.matrix.androidsdk.MXSession;
+import org.matrix.androidsdk.data.Room;
+import org.matrix.androidsdk.data.RoomPreviewData;
+import org.matrix.androidsdk.rest.callback.ApiCallback;
+import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
+import org.matrix.androidsdk.rest.model.MatrixError;
+import org.matrix.androidsdk.rest.model.group.Group;
+import org.matrix.androidsdk.rest.model.group.GroupRoom;
+import org.matrix.androidsdk.rest.model.group.GroupUser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import im.vector.activity.CommonActivityUtils;
+import im.vector.activity.VectorMemberDetailsActivity;
+import im.vector.activity.VectorRoomActivity;
+
+public class GroupUtils {
+ private static final String LOG_TAG = GroupUtils.class.getSimpleName();
+
+ /**
+ * Create a list of groups by filtering the given list with the given pattern
+ *
+ * @param groupsToFilter
+ * @param constraint
+ * @return filtered groups
+ */
+ public static List getFilteredGroups(final List groupsToFilter, final CharSequence constraint) {
+ final String filterPattern = constraint != null ? constraint.toString().trim() : null;
+ if (!TextUtils.isEmpty(filterPattern)) {
+ List filteredGroups = new ArrayList<>();
+ Pattern pattern = Pattern.compile(Pattern.quote(filterPattern), Pattern.CASE_INSENSITIVE);
+
+ for (final Group group : groupsToFilter) {
+ if (pattern.matcher(group.getDisplayName()).find()) {
+ filteredGroups.add(group);
+ }
+ }
+ return filteredGroups;
+ } else {
+ return groupsToFilter;
+ }
+ }
+
+ /**
+ * Create a list of groups by filtering the given list with the given pattern
+ *
+ * @param groupsUsersToFilter
+ * @param constraint
+ * @return filtered group users
+ */
+ public static List getFilteredGroupUsers(final List groupsUsersToFilter, final CharSequence constraint) {
+ final String filterPattern = constraint != null ? constraint.toString().trim() : null;
+ if (!TextUtils.isEmpty(filterPattern)) {
+ List filteredGroupUsers = new ArrayList<>();
+ Pattern pattern = Pattern.compile(Pattern.quote(filterPattern), Pattern.CASE_INSENSITIVE);
+
+ for (final GroupUser groupUser : groupsUsersToFilter) {
+ if (pattern.matcher(groupUser.getDisplayname()).find()) {
+ filteredGroupUsers.add(groupUser);
+ }
+ }
+ return filteredGroupUsers;
+ } else {
+ return groupsUsersToFilter;
+ }
+ }
+
+ /**
+ * Create a list of groups by filtering the given list with the given pattern
+ *
+ * @param groupRoomsToFilter
+ * @param constraint
+ * @return filtered group users
+ */
+ public static List getFilteredGroupRooms(final List groupRoomsToFilter, final CharSequence constraint) {
+ final String filterPattern = constraint != null ? constraint.toString().trim() : null;
+ if (!TextUtils.isEmpty(filterPattern)) {
+ List filteredGroupRooms = new ArrayList<>();
+ Pattern pattern = Pattern.compile(Pattern.quote(filterPattern), Pattern.CASE_INSENSITIVE);
+
+ for (final GroupRoom groupRoom : groupRoomsToFilter) {
+ if (pattern.matcher(groupRoom.getDisplayName()).find()) {
+ filteredGroupRooms.add(groupRoom);
+ }
+ }
+ return filteredGroupRooms;
+ } else {
+ return groupRoomsToFilter;
+ }
+ }
+
+ /**
+ * Open the detailed group user page
+ *
+ * @param fromActivity the caller activity
+ * @param session the session
+ * @param groupUser the group user
+ */
+ public static void openGroupUserPage(Activity fromActivity, MXSession session, GroupUser groupUser) {
+ Intent userIntent = new Intent(fromActivity, VectorMemberDetailsActivity.class);
+ userIntent.putExtra(VectorMemberDetailsActivity.EXTRA_MEMBER_ID, groupUser.userId);
+
+ if (!TextUtils.isEmpty(groupUser.avatarUrl)) {
+ userIntent.putExtra(VectorMemberDetailsActivity.EXTRA_MEMBER_AVATAR_URL, groupUser.avatarUrl);
+ }
+
+ if (!TextUtils.isEmpty(groupUser.displayname)) {
+ userIntent.putExtra(VectorMemberDetailsActivity.EXTRA_MEMBER_DISPLAY_NAME, groupUser.displayname);
+ }
+
+ userIntent.putExtra(VectorMemberDetailsActivity.EXTRA_MATRIX_ID, session.getCredentials().userId);
+ fromActivity.startActivity(userIntent);
+ }
+
+ /**
+ * Open the detailed group room page
+ *
+ * @param fromActivity the caller activity
+ * @param session the session
+ * @param groupRoom the group room
+ */
+ public static void openGroupRoom(final Activity fromActivity, final MXSession session, final GroupRoom groupRoom, final SimpleApiCallback callback) {
+ Room room = session.getDataHandler().getStore().getRoom(groupRoom.roomId);
+
+ if ((null == room) || (null == room.getMember(session.getMyUserId()))) {
+ final RoomPreviewData roomPreviewData = new RoomPreviewData(session, groupRoom.roomId, null, groupRoom.getAlias(), null);
+
+ roomPreviewData.fetchPreviewData(new ApiCallback() {
+ private void onDone() {
+ if (null != callback) {
+ callback.onSuccess(null);
+ }
+
+ CommonActivityUtils.previewRoom(fromActivity, roomPreviewData);
+ }
+
+ @Override
+ public void onSuccess(Void info) {
+ onDone();
+ }
+
+ private void onError() {
+ roomPreviewData.setRoomState(groupRoom);
+ roomPreviewData.setRoomName(groupRoom.name);
+ onDone();
+ }
+
+ @Override
+ public void onNetworkError(Exception e) {
+ onError();
+ }
+
+ @Override
+ public void onMatrixError(MatrixError e) {
+ onError();
+ }
+
+ @Override
+ public void onUnexpectedError(Exception e) {
+ onError();
+ }
+ });
+ } else {
+ Intent roomIntent = new Intent(fromActivity, VectorRoomActivity.class);
+ roomIntent.putExtra(VectorRoomActivity.EXTRA_MATRIX_ID, session.getMyUserId());
+ roomIntent.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, groupRoom.roomId);
+ fromActivity.startActivity(roomIntent);
+
+ if (null != callback) {
+ callback.onSuccess(null);
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/util/MatrixURLSpan.java b/vector/src/main/java/im/vector/util/MatrixURLSpan.java
index 9da5473b6e..ec9444002e 100755
--- a/vector/src/main/java/im/vector/util/MatrixURLSpan.java
+++ b/vector/src/main/java/im/vector/util/MatrixURLSpan.java
@@ -130,6 +130,10 @@ public void onClick(View widget) {
if (null != mActionsListener) {
mActionsListener.onMessageIdClick(mURL);
}
+ } else if (mPattern == MXSession.PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER) {
+ if (null != mActionsListener) {
+ mActionsListener.onGroupIdClick(mURL);
+ }
} else {
Uri uri = Uri.parse(getURL());
@@ -156,7 +160,8 @@ public void onClick(View widget) {
MXSession.PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER,
MXSession.PATTERN_CONTAIN_MATRIX_ALIAS,
MXSession.PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER,
- MXSession.PATTERN_CONTAIN_MATRIX_MESSAGE_IDENTIFIER
+ MXSession.PATTERN_CONTAIN_MATRIX_MESSAGE_IDENTIFIER,
+ MXSession.PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
);
/**
diff --git a/vector/src/main/java/im/vector/util/PhoneNumberUtils.java b/vector/src/main/java/im/vector/util/PhoneNumberUtils.java
index 6e8e3e3eed..44f6f3ff82 100755
--- a/vector/src/main/java/im/vector/util/PhoneNumberUtils.java
+++ b/vector/src/main/java/im/vector/util/PhoneNumberUtils.java
@@ -23,7 +23,6 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
@@ -160,7 +159,7 @@ public static String getCountryCode(final Context context) {
if (!preferences.contains(COUNTRY_CODE_PREF_KEY) || TextUtils.isEmpty(preferences.getString(COUNTRY_CODE_PREF_KEY, ""))) {
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- String countryCode = tm.getNetworkCountryIso().toUpperCase();
+ String countryCode = tm.getNetworkCountryIso().toUpperCase(VectorApp.getApplicationLocale());
if (TextUtils.isEmpty(countryCode)
&& !TextUtils.isEmpty(Locale.getDefault().getCountry())
&& PhoneNumberUtil.getInstance().getCountryCodeForRegion(Locale.getDefault().getCountry()) != 0) {
diff --git a/vector/src/main/java/im/vector/util/PreferencesManager.java b/vector/src/main/java/im/vector/util/PreferencesManager.java
index 263fcdb1aa..3acc21e259 100755
--- a/vector/src/main/java/im/vector/util/PreferencesManager.java
+++ b/vector/src/main/java/im/vector/util/PreferencesManager.java
@@ -18,7 +18,6 @@
import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.media.RingtoneManager;
@@ -121,8 +120,17 @@ public class PreferencesManager {
private static final String SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY";
public static final String SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY";
+ public static final String SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY";
+
private static final String SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY = "SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY";
+ public static final String SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY";
+
+ private static final String SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY";
+
+ private static final String SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY";
+
+ private static final String SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY";
private static final int MEDIA_SAVING_3_DAYS = 0;
private static final int MEDIA_SAVING_1_WEEK = 1;
@@ -193,18 +201,18 @@ public static void clearPreferences(Context context) {
}
/**
- * Tells if a background service can be started.
+ * Tells if the battery optimisations are ignored for this application.
*
* @param context the context
- * @return true if a background service can be started.
+ * @return true if the battery optimisations are ignored.
*/
@SuppressLint("NewApi")
- public static boolean canStartBackgroundService(Context context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- return ((PowerManager) context.getSystemService(context.POWER_SERVICE)).isIgnoringBatteryOptimizations(context.getPackageName());
+ public static boolean useBatteryOptimisation(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return !((PowerManager) context.getSystemService(context.POWER_SERVICE)).isIgnoringBatteryOptimizations(context.getPackageName());
}
- return true;
+ return false;
}
/**
@@ -606,9 +614,52 @@ public static boolean pinUnreadMessages(Context context) {
* Tells if Piwik can be used
*
* @param context the context
- * @return null if not defined, true / false when defined
+ * @return true to use it
*/
- public static Boolean trackWithPiwik(Context context) {
+ public static boolean trackWithPiwik(Context context) {
return !PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_DISABLE_PIWIK_SETTINGS_PREFERENCE_KEY, false);
}
+
+ /**
+ * Tells if the phone must vibrate when mentioning
+ *
+ * @param context the context
+ * @return true
+ */
+ public static boolean vibrateWhenMentioning(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_VIBRATE_ON_MENTION_KEY, false);
+ }
+
+ /**
+ * Tells if the rage shake is used.
+ *
+ * @param context the context
+ * @return true if the rage shake is used
+ */
+ public static boolean useRageshake(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true);
+ }
+
+ /**
+ * Update the rage shake status.
+ *
+ * @param context the context
+ * @param isEnabled true to enable the rage shake
+ */
+ public static void setUseRageshake(Context context, boolean isEnabled) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled);
+ editor.commit();
+ }
+
+ /**
+ * Tells if all the events must be displayed ie even the redacted events.
+ *
+ * @param context the context
+ * @return true to display all the events even the redacted ones.
+ */
+ public static boolean displayAllEvents(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false);
+ }
}
diff --git a/vector/src/main/java/im/vector/util/RageShake.java b/vector/src/main/java/im/vector/util/RageShake.java
index bb5989f1e5..e6a4c0d9d1 100755
--- a/vector/src/main/java/im/vector/util/RageShake.java
+++ b/vector/src/main/java/im/vector/util/RageShake.java
@@ -41,10 +41,28 @@ public class RageShake implements SensorEventListener {
// the context
private Context mContext;
+ // the sensor
+ private SensorManager mSensorManager;
+ private Sensor mSensor;
+ private boolean mIsStarted;
+
/**
* Constructor
*/
- public RageShake() {
+ public RageShake(Context context) {
+ mContext = context;
+
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+
+ if (null != mSensorManager) {
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ }
+
+ if (null == mSensor) {
+ Log.e(LOG_TAG, "No accelerometer in this device. Cannot use rage shake.");
+ mSensorManager = null;
+ }
+
// Samsung devices for some reason seem to be less sensitive than others so the threshold is being
// lowered for them. A possible lead for a better formula is the fact that the sensitivity detected
// with the calculated force below seems to relate to the sample rate: The higher the sample rate,
@@ -79,12 +97,7 @@ public void onClick(DialogInterface dialog, int which) {
.setNeutralButton(R.string.disable, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
-
- SharedPreferences.Editor editor = preferences.edit();
- editor.putBoolean(mContext.getString(im.vector.R.string.settings_key_use_rage_shake), false);
- editor.commit();
-
+ PreferencesManager.setUseRageshake(mContext, false);
dialog.dismiss();
}
})
@@ -101,19 +114,27 @@ public void onClick(DialogInterface dialog, int which) {
}
}
+
/**
* start the sensor detector
*/
- public void start(Context context) {
- mContext = context;
+ public void start() {
+ if ((null != mSensorManager) && PreferencesManager.useRageshake(mContext) && !VectorApp.isAppInBackground() && !mIsStarted) {
+ mIsStarted = true;
+ mLastUpdate = 0;
+ mLastShake = 0;
+ mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+ }
- SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- Sensor s = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- if (s == null) {
- Log.e(LOG_TAG, "No accelerometer in this device. Cannot use rage shake.");
- return;
+ /**
+ * Stop the sensor detector
+ */
+ public void stop() {
+ if (null != mSensorManager) {
+ mSensorManager.unregisterListener(this, mSensor);
}
- sm.registerListener(this, s, SensorManager.SENSOR_DELAY_NORMAL);
+ mIsStarted = false;
}
@Override
@@ -136,12 +157,6 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) {
@Override
public void onSensorChanged(SensorEvent event) {
- // ignore the sensor events when the application is in background
- if (VectorApp.isAppInBackground()) {
- mLastUpdate = 0;
- return;
- }
-
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
return;
}
@@ -169,9 +184,7 @@ public void onSensorChanged(SensorEvent event) {
Log.d(LOG_TAG, "Shaking detected.");
mLastShakeTimestamp = System.currentTimeMillis();
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
-
- if (preferences.getBoolean(mContext.getString(im.vector.R.string.settings_key_use_rage_shake), true)) {
+ if (PreferencesManager.useRageshake(mContext)) {
promptForReport();
}
} else {
diff --git a/vector/src/main/java/im/vector/util/RoomUtils.java b/vector/src/main/java/im/vector/util/RoomUtils.java
index 0a9b7384d5..bd54cb8ce0 100644
--- a/vector/src/main/java/im/vector/util/RoomUtils.java
+++ b/vector/src/main/java/im/vector/util/RoomUtils.java
@@ -20,12 +20,20 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Icon;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.TextUtils;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
+import android.widget.ImageView;
import android.widget.PopupMenu;
import org.matrix.androidsdk.MXSession;
@@ -36,11 +44,13 @@
import org.matrix.androidsdk.data.store.IMXStore;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.model.Event;
+import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.util.BingRulesManager;
import org.matrix.androidsdk.util.EventDisplay;
import org.matrix.androidsdk.util.Log;
+import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -53,6 +63,7 @@
import im.vector.Matrix;
import im.vector.R;
import im.vector.activity.CommonActivityUtils;
+import im.vector.activity.VectorRoomActivity;
import im.vector.adapters.AdapterUtils;
public class RoomUtils {
@@ -60,7 +71,7 @@ public class RoomUtils {
private static final String LOG_TAG = RoomUtils.class.getSimpleName();
public interface MoreActionListener {
- void onToggleRoomNotifications(MXSession session, String roomId);
+ void onUpdateRoomNotificationsState(MXSession session, String roomId, BingRulesManager.RoomNotificationState state);
void onToggleDirectChat(MXSession session, String roomId);
@@ -71,6 +82,10 @@ public interface MoreActionListener {
void moveToLowPriority(MXSession session, String roomId);
void onLeaveRoom(MXSession session, String roomId);
+
+ void onForgetRoom(MXSession session, String roomId);
+
+ void addHomeScreenShortcut(MXSession session, String roomId);
}
public interface HistoricalRoomActionListener {
@@ -450,18 +465,21 @@ private static void displayPopupMenu(final Context context, final MXSession sess
return;
}
+ Context popmenuContext = new ContextThemeWrapper(context, R.style.PopMenuStyle);
+
final PopupMenu popup;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- popup = new PopupMenu(context, actionView, Gravity.END);
+ popup = new PopupMenu(popmenuContext, actionView, Gravity.END);
} else {
- popup = new PopupMenu(context, actionView);
+ popup = new PopupMenu(popmenuContext, actionView);
}
popup.getMenuInflater().inflate(R.menu.vector_home_room_settings, popup.getMenu());
CommonActivityUtils.tintMenuIcons(popup.getMenu(), ThemeUtils.getColor(context, R.attr.settings_icon_tint_color));
if (room.isLeft()) {
popup.getMenu().setGroupVisible(R.id.active_room_actions, false);
+ popup.getMenu().setGroupVisible(R.id.add_shortcut_actions, false);
popup.getMenu().setGroupVisible(R.id.historical_room_actions, true);
if (historicalRoomActionListener != null) {
@@ -477,42 +495,92 @@ public boolean onMenuItemClick(final MenuItem item) {
}
} else {
popup.getMenu().setGroupVisible(R.id.active_room_actions, true);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ popup.getMenu().setGroupVisible(R.id.add_shortcut_actions, false);
+ } else {
+ ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+ if (!manager.isRequestPinShortcutSupported()) {
+ popup.getMenu().setGroupVisible(R.id.add_shortcut_actions, false);
+ } else {
+ popup.getMenu().setGroupVisible(R.id.add_shortcut_actions, true);
+ }
+ }
+
popup.getMenu().setGroupVisible(R.id.historical_room_actions, false);
MenuItem item;
- final BingRulesManager bingRulesManager = session.getDataHandler().getBingRulesManager();
+ BingRulesManager.RoomNotificationState state = session.getDataHandler().getBingRulesManager().getRoomNotificationState(room.getRoomId());
+
+ if (BingRulesManager.RoomNotificationState.ALL_MESSAGES_NOISY != state) {
+ item = popup.getMenu().findItem(R.id.ic_action_notifications_noisy);
+ item.setIcon(null);
+ }
+
+ if (BingRulesManager.RoomNotificationState.ALL_MESSAGES != state) {
+ item = popup.getMenu().findItem(R.id.ic_action_notifications_all_message);
+ item.setIcon(null);
+ }
+
+ if (BingRulesManager.RoomNotificationState.MENTIONS_ONLY != state) {
+ item = popup.getMenu().findItem(R.id.ic_action_notifications_mention_only);
+ item.setIcon(null);
+ }
- if (bingRulesManager.isRoomNotificationsDisabled(room.getRoomId())) {
- item = popup.getMenu().getItem(0);
+ if (BingRulesManager.RoomNotificationState.MUTE != state) {
+ item = popup.getMenu().findItem(R.id.ic_action_notifications_mute);
item.setIcon(null);
}
if (!isFavorite) {
- item = popup.getMenu().getItem(1);
+ item = popup.getMenu().findItem(R.id.ic_action_select_fav);
item.setIcon(null);
}
if (!isLowPrior) {
- item = popup.getMenu().getItem(2);
+ item = popup.getMenu().findItem(R.id.ic_action_select_deprioritize);
item.setIcon(null);
}
- if (session.getDirectChatRoomIdsList().indexOf(room.getRoomId()) < 0) {
- item = popup.getMenu().getItem(3);
+ if (!session.getDirectChatRoomIdsList().contains(room.getRoomId())) {
+ item = popup.getMenu().findItem(R.id.ic_action_select_direct_chat);
item.setIcon(null);
}
+ RoomMember member = room.getMember(session.getMyUserId());
+ final boolean isBannedKickedRoom = (null != member) && member.kickedOrBanned();
+
+ if (isBannedKickedRoom) {
+ item = popup.getMenu().findItem(R.id.ic_action_select_remove);
+
+ if (null != item) {
+ item.setTitle(R.string.forget_room);
+ }
+ }
if (moreActionListener != null) {
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem item) {
switch (item.getItemId()) {
- case R.id.ic_action_select_notifications: {
- moreActionListener.onToggleRoomNotifications(session, room.getRoomId());
+ case R.id.ic_action_notifications_noisy:
+ moreActionListener.onUpdateRoomNotificationsState(session, room.getRoomId(), BingRulesManager.RoomNotificationState.ALL_MESSAGES_NOISY);
break;
- }
+
+ case R.id.ic_action_notifications_all_message:
+ moreActionListener.onUpdateRoomNotificationsState(session, room.getRoomId(), BingRulesManager.RoomNotificationState.ALL_MESSAGES);
+ break;
+
+ case R.id.ic_action_notifications_mention_only:
+ moreActionListener.onUpdateRoomNotificationsState(session, room.getRoomId(), BingRulesManager.RoomNotificationState.MENTIONS_ONLY);
+ break;
+
+ case R.id.ic_action_notifications_mute:
+ moreActionListener.onUpdateRoomNotificationsState(session, room.getRoomId(), BingRulesManager.RoomNotificationState.MUTE);
+ break;
+
case R.id.ic_action_select_fav: {
if (isFavorite) {
moreActionListener.moveToConversations(session, room.getRoomId());
@@ -530,13 +598,21 @@ public boolean onMenuItemClick(final MenuItem item) {
break;
}
case R.id.ic_action_select_remove: {
- moreActionListener.onLeaveRoom(session, room.getRoomId());
+ if (isBannedKickedRoom) {
+ moreActionListener.onForgetRoom(session, room.getRoomId());
+ } else {
+ moreActionListener.onLeaveRoom(session, room.getRoomId());
+ }
break;
}
case R.id.ic_action_select_direct_chat: {
moreActionListener.onToggleDirectChat(session, room.getRoomId());
break;
}
+ case R.id.ic_action_add_homescreen_shortcut: {
+ moreActionListener.addHomeScreenShortcut(session, room.getRoomId());
+ break;
+ }
}
return false;
}
@@ -591,6 +667,75 @@ public void onClick(DialogInterface dialog, int which) {
.show();
}
+ /**
+ * Add a room shortcut to the home screen (Android >= O).
+ *
+ * @param context the context
+ * @param session the session
+ * @param roomId the room Id
+ */
+ @SuppressLint("NewApi")
+ public static void addHomeScreenShortcut(final Context context, final MXSession session, final String roomId) {
+ // android >= O only
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return;
+ }
+
+ ShortcutManager manager = context.getSystemService(ShortcutManager.class);
+
+ if (!manager.isRequestPinShortcutSupported()) {
+ return;
+ }
+
+ Room room = session.getDataHandler().getRoom(roomId);
+
+ if (null == room) {
+ return;
+ }
+
+ String roomName = VectorUtils.getRoomDisplayName(context, session, room);
+
+ Bitmap bitmap = null;
+
+ // try to retrieve the avatar from the medias cache
+ if (!TextUtils.isEmpty(room.getAvatarUrl())) {
+ int size = context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size);
+
+ // check if the thumbnail is already downloaded
+ File f = session.getMediasCache().thumbnailCacheFile(room.getAvatarUrl(), size);
+
+ if (null != f) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ try {
+ bitmap = BitmapFactory.decodeFile(f.getPath(), options);
+ } catch (OutOfMemoryError oom) {
+ Log.e(LOG_TAG, "decodeFile failed with an oom");
+ }
+ }
+ }
+
+ if (null == bitmap) {
+ bitmap = VectorUtils.getAvatar(context, VectorUtils.getAvatarColor(roomId), roomName, true);
+ }
+
+ Icon icon = Icon.createWithBitmap(bitmap);
+
+ Intent intent = new Intent(context, VectorRoomActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId);
+
+ ShortcutInfo info = new ShortcutInfo.Builder(context, roomId)
+ .setShortLabel(roomName)
+ .setIcon(icon)
+ .setIntent(intent)
+ .build();
+
+
+ manager.requestPinShortcut(info, null);
+ }
+
/**
* Update a room Tag
*
@@ -643,18 +788,6 @@ public static void toggleDirectChat(final MXSession session, String roomId, fina
}
}
- /**
- * Enable or disable notifications for the given room
- *
- * @param session
- * @param roomId
- * @param listener
- */
- public static void toggleNotifications(final MXSession session, final String roomId, final BingRulesManager.onBingRuleUpdateListener listener) {
- BingRulesManager bingRulesManager = session.getDataHandler().getBingRulesManager();
- bingRulesManager.muteRoomNotifications(roomId, !bingRulesManager.isRoomNotificationsDisabled(roomId), listener);
- }
-
/**
* Get whether the room of the given is a direct chat
*
diff --git a/vector/src/main/java/im/vector/util/SlashComandsParser.java b/vector/src/main/java/im/vector/util/SlashComandsParser.java
index 14487c6cf3..18a9d5e138 100755
--- a/vector/src/main/java/im/vector/util/SlashComandsParser.java
+++ b/vector/src/main/java/im/vector/util/SlashComandsParser.java
@@ -17,7 +17,6 @@
package im.vector.util;
import android.app.AlertDialog;
-import android.content.DialogInterface;
import android.text.TextUtils;
import org.matrix.androidsdk.util.Log;
diff --git a/vector/src/main/java/im/vector/util/SlidableMediaInfo.java b/vector/src/main/java/im/vector/util/SlidableMediaInfo.java
index 7a9343c363..799ea5adfd 100755
--- a/vector/src/main/java/im/vector/util/SlidableMediaInfo.java
+++ b/vector/src/main/java/im/vector/util/SlidableMediaInfo.java
@@ -15,7 +15,7 @@
*/
package im.vector.util;
-import org.matrix.androidsdk.rest.model.EncryptedFileInfo;
+import org.matrix.androidsdk.rest.model.crypto.EncryptedFileInfo;
import java.io.Serializable;
diff --git a/vector/src/main/java/im/vector/util/ThemeUtils.java b/vector/src/main/java/im/vector/util/ThemeUtils.java
index 657a851c31..2053bbea8d 100644
--- a/vector/src/main/java/im/vector/util/ThemeUtils.java
+++ b/vector/src/main/java/im/vector/util/ThemeUtils.java
@@ -23,6 +23,7 @@
import android.preference.PreferenceManager;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
+import android.support.design.widget.TabLayout;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.TypedValue;
@@ -47,6 +48,7 @@
import im.vector.activity.SplashActivity;
import im.vector.activity.VectorBaseSearchActivity;
import im.vector.activity.VectorCallViewActivity;
+import im.vector.activity.VectorGroupDetailsActivity;
import im.vector.activity.VectorHomeActivity;
import im.vector.activity.VectorMediasPickerActivity;
import im.vector.activity.VectorMediasViewerActivity;
@@ -178,6 +180,8 @@ public static void setActivityTheme(Activity activity) {
activity.setTheme(R.style.AppTheme_Dark);
} else if (activity instanceof LockScreenActivity) {
activity.setTheme(R.style.Vector_Lock_Dark);
+ } else if (activity instanceof VectorGroupDetailsActivity) {
+ activity.setTheme(R.style.AppTheme_Dark);
}
}
@@ -234,6 +238,8 @@ public static void setActivityTheme(Activity activity) {
activity.setTheme(R.style.AppTheme_Black);
} else if (activity instanceof LockScreenActivity) {
activity.setTheme(R.style.Vector_Lock_Black);
+ } else if (activity instanceof VectorGroupDetailsActivity) {
+ activity.setTheme(R.style.AppTheme_Black);
}
}
@@ -247,6 +253,34 @@ public static void setActivityTheme(Activity activity) {
mColorByAttr.clear();
}
+ /**
+ * Set the TabLayout colors.
+ * It seems that there is no proper way to manage it with the manifest file.
+ *
+ * @param activity the activity
+ * @param layout the layout
+ */
+ public static void setTabLayoutTheme(Activity activity, TabLayout layout) {
+
+ if (activity instanceof VectorGroupDetailsActivity) {
+ int textColor;
+ int underlineColor;
+ int backgroundColor;
+
+ if (TextUtils.equals(getApplicationTheme(activity), THEME_LIGHT_VALUE)) {
+ underlineColor = textColor = ContextCompat.getColor(activity, android.R.color.white);
+ backgroundColor = ContextCompat.getColor(activity, R.color.tab_groups);
+ } else {
+ underlineColor = textColor = ContextCompat.getColor(activity, R.color.tab_groups);
+ backgroundColor = getColor(activity, R.attr.primary_color);
+ }
+
+ layout.setTabTextColors(textColor, textColor);
+ layout.setSelectedTabIndicatorColor(underlineColor);
+ layout.setBackgroundColor(backgroundColor);
+ }
+ }
+
/**
* Translates color attributes to colors
*
diff --git a/vector/src/main/java/im/vector/util/VectorImageGetter.java b/vector/src/main/java/im/vector/util/VectorImageGetter.java
new file mode 100644
index 0000000000..3c60804f77
--- /dev/null
+++ b/vector/src/main/java/im/vector/util/VectorImageGetter.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017 Vector Creations Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.util;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.support.v4.content.res.ResourcesCompat;
+
+import org.matrix.androidsdk.MXSession;
+import org.matrix.androidsdk.util.Log;
+
+import android.text.Html;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import im.vector.R;
+import im.vector.VectorApp;
+
+public class VectorImageGetter implements Html.ImageGetter {
+ private final String LOG_TAG = VectorImageGetter.class.getSimpleName();
+
+ // application image placeholder
+ private static Drawable mPlaceHolder = null;
+
+ // source to image map
+ private Map mBitmapCache = new HashMap<>();
+
+ // pending source downloads
+ private Set mPendingDownloads = new HashSet<>();
+
+ /**
+ * Image download listener
+ */
+ public interface OnImageDownloadListener {
+ /**
+ * An image has been downloaded.
+ *
+ * @param source the image URL
+ */
+ void onImageDownloaded(String source);
+ }
+
+ //
+ private MXSession mSession;
+
+ // listener
+ private OnImageDownloadListener mListener;
+
+ /**
+ * Constructor
+ *
+ * @param session the session
+ */
+ public VectorImageGetter(MXSession session) {
+ mSession = session;
+ }
+
+ /**
+ * Set the listener
+ *
+ * @param listener the listener
+ */
+ public void setListener(OnImageDownloadListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public Drawable getDrawable(String source) {
+ if (mBitmapCache.containsKey(source)) {
+ Log.d(LOG_TAG, "## getDrawable() : " + source + " already cached");
+ return mBitmapCache.get(source);
+ }
+
+ if (!mPendingDownloads.contains(source)) {
+ Log.d(LOG_TAG, "## getDrawable() : starts a task to download " + source);
+ try {
+ new ImageDownloaderTask().execute(source);
+ mPendingDownloads.add(source);
+ } catch (Throwable t) {
+ Log.e(LOG_TAG, "## getDrawable() failed " + t.getMessage());
+ }
+ } else {
+ Log.d(LOG_TAG, "## getDrawable() : " + source + " is downloading");
+ }
+
+ if (null == mPlaceHolder) {
+ mPlaceHolder = ResourcesCompat.getDrawable(VectorApp.getInstance().getResources(), R.drawable.filetype_image, null);
+ mPlaceHolder.setBounds(0, 0, mPlaceHolder.getIntrinsicWidth(), mPlaceHolder.getIntrinsicHeight());
+ }
+
+ return mPlaceHolder;
+ }
+
+
+ private class ImageDownloaderTask extends AsyncTask
+ android:text="@string/auth_recaptcha_message"
+ android:textSize="16sp" />
-
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/adapter_item_circular_room_view.xml b/vector/src/main/res/layout/adapter_item_circular_room_view.xml
old mode 100644
new mode 100755
diff --git a/vector/src/main/res/layout/adapter_item_group_invite.xml b/vector/src/main/res/layout/adapter_item_group_invite.xml
new file mode 100644
index 0000000000..ac39a98548
--- /dev/null
+++ b/vector/src/main/res/layout/adapter_item_group_invite.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/adapter_item_group_user_room_view.xml b/vector/src/main/res/layout/adapter_item_group_user_room_view.xml
new file mode 100644
index 0000000000..2414f77952
--- /dev/null
+++ b/vector/src/main/res/layout/adapter_item_group_user_room_view.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/adapter_item_group_view.xml b/vector/src/main/res/layout/adapter_item_group_view.xml
new file mode 100644
index 0000000000..f8e22e3e28
--- /dev/null
+++ b/vector/src/main/res/layout/adapter_item_group_view.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/adapter_item_room_invite.xml b/vector/src/main/res/layout/adapter_item_room_invite.xml
index 397f399a83..40203bdfde 100644
--- a/vector/src/main/res/layout/adapter_item_room_invite.xml
+++ b/vector/src/main/res/layout/adapter_item_room_invite.xml
@@ -17,7 +17,6 @@
android:layout_height="@dimen/chat_avatar_size"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
- android:layout_toRightOf="@+id/indicator_unread_message"
tools:src="#22000000" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/adapter_item_vector_message_code_block.xml b/vector/src/main/res/layout/adapter_item_vector_message_code_block.xml
new file mode 100644
index 0000000000..72c18f065a
--- /dev/null
+++ b/vector/src/main/res/layout/adapter_item_vector_message_code_block.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/adapter_item_vector_message_code_text.xml b/vector/src/main/res/layout/adapter_item_vector_message_code_text.xml
new file mode 100644
index 0000000000..dac630b074
--- /dev/null
+++ b/vector/src/main/res/layout/adapter_item_vector_message_code_text.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/vector/src/main/res/layout/adapter_item_vector_message_merge.xml b/vector/src/main/res/layout/adapter_item_vector_message_merge.xml
index 0b95087fb7..41905e293b 100644
--- a/vector/src/main/res/layout/adapter_item_vector_message_merge.xml
+++ b/vector/src/main/res/layout/adapter_item_vector_message_merge.xml
@@ -60,7 +60,6 @@
android:id="@+id/messagesAdapter_merge_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
android:text="A message here"
android:textIsSelectable="false"
android:textSize="14sp" />
diff --git a/vector/src/main/res/layout/adapter_item_vector_message_text_emote_notice.xml b/vector/src/main/res/layout/adapter_item_vector_message_text_emote_notice.xml
index a1b345f0b1..5aaae77d5c 100644
--- a/vector/src/main/res/layout/adapter_item_vector_message_text_emote_notice.xml
+++ b/vector/src/main/res/layout/adapter_item_vector_message_text_emote_notice.xml
@@ -112,4 +112,11 @@
+
+
+
diff --git a/vector/src/main/res/layout/adapter_vector_medias_viewer.xml b/vector/src/main/res/layout/adapter_vector_medias_viewer.xml
index 87a6ec32e4..cf323861ec 100644
--- a/vector/src/main/res/layout/adapter_vector_medias_viewer.xml
+++ b/vector/src/main/res/layout/adapter_vector_medias_viewer.xml
@@ -42,4 +42,10 @@
android:alpha="0.2"
android:layout_width="160dp"
android:layout_height="160dp"/>
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/dialog_create_group.xml b/vector/src/main/res/layout/dialog_create_group.xml
new file mode 100644
index 0000000000..6c7a34004c
--- /dev/null
+++ b/vector/src/main/res/layout/dialog_create_group.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_dialog_groups_list.xml b/vector/src/main/res/layout/fragment_dialog_groups_list.xml
new file mode 100644
index 0000000000..76abb033ca
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_dialog_groups_list.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_group_details_home.xml b/vector/src/main/res/layout/fragment_group_details_home.xml
new file mode 100644
index 0000000000..ee65cd110b
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_group_details_home.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_group_details_people.xml b/vector/src/main/res/layout/fragment_group_details_people.xml
new file mode 100644
index 0000000000..cfd5849ca6
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_group_details_people.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_group_details_rooms.xml b/vector/src/main/res/layout/fragment_group_details_rooms.xml
new file mode 100644
index 0000000000..cfd5849ca6
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_group_details_rooms.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_groups.xml b/vector/src/main/res/layout/fragment_groups.xml
new file mode 100644
index 0000000000..e3b960a1a0
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_groups.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_room_directory.xml b/vector/src/main/res/layout/item_room_directory.xml
index 9e14d6341e..07cb8222b9 100644
--- a/vector/src/main/res/layout/item_room_directory.xml
+++ b/vector/src/main/res/layout/item_room_directory.xml
@@ -27,7 +27,6 @@
android:id="@+id/room_directory_display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
@@ -38,7 +37,6 @@
android:id="@+id/room_directory_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
android:ellipsize="end"
android:maxLines="1"
android:textSize="14sp"
diff --git a/vector/src/main/res/layout/pill_view.xml b/vector/src/main/res/layout/pill_view.xml
index fd37aa2878..ed801a53f3 100644
--- a/vector/src/main/res/layout/pill_view.xml
+++ b/vector/src/main/res/layout/pill_view.xml
@@ -8,13 +8,20 @@
android:layout_marginRight="3dp"
android:orientation="horizontal">
+
+
-
diff --git a/vector/src/main/res/layout/url_preview_view.xml b/vector/src/main/res/layout/url_preview_view.xml
new file mode 100644
index 0000000000..90ee0889a6
--- /dev/null
+++ b/vector/src/main/res/layout/url_preview_view.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/vector_incoming_call_dialog.xml b/vector/src/main/res/layout/vector_incoming_call_dialog.xml
deleted file mode 100644
index 9255bb374b..0000000000
--- a/vector/src/main/res/layout/vector_incoming_call_dialog.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/vector/src/main/res/layout/vector_incoming_call_round_avatar.xml b/vector/src/main/res/layout/vector_incoming_call_round_avatar.xml
deleted file mode 100644
index aec2dad66b..0000000000
--- a/vector/src/main/res/layout/vector_incoming_call_round_avatar.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/vector/src/main/res/layout/vector_message_flair_groups_list.xml b/vector/src/main/res/layout/vector_message_flair_groups_list.xml
new file mode 100644
index 0000000000..3fa83dc155
--- /dev/null
+++ b/vector/src/main/res/layout/vector_message_flair_groups_list.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/vector_message_header.xml b/vector/src/main/res/layout/vector_message_header.xml
index 48cd6e7c3b..89e09f7ed6 100644
--- a/vector/src/main/res/layout/vector_message_header.xml
+++ b/vector/src/main/res/layout/vector_message_header.xml
@@ -1,11 +1,11 @@
-
+ android:orientation="vertical">
+
+
+ android:textStyle="bold" />
-
\ No newline at end of file
diff --git a/vector/src/main/res/layout/vector_message_sender.xml b/vector/src/main/res/layout/vector_message_sender.xml
index d3495bcb5a..787241f0b4 100644
--- a/vector/src/main/res/layout/vector_message_sender.xml
+++ b/vector/src/main/res/layout/vector_message_sender.xml
@@ -23,4 +23,8 @@
android:textSize="14sp"
android:textStyle="bold"
tools:text="One very long sender name for testing purpose" />
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/vector_settings_round_group_avatar.xml b/vector/src/main/res/layout/vector_settings_round_group_avatar.xml
new file mode 100644
index 0000000000..015d269948
--- /dev/null
+++ b/vector/src/main/res/layout/vector_settings_round_group_avatar.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/menu/bottom_navigation_main.xml b/vector/src/main/res/menu/bottom_navigation_main.xml
index 9da28aaf3c..c4a3a65238 100644
--- a/vector/src/main/res/menu/bottom_navigation_main.xml
+++ b/vector/src/main/res/menu/bottom_navigation_main.xml
@@ -27,6 +27,13 @@
android:contentDescription="@string/bottom_action_rooms"
android:id="@+id/bottom_action_rooms"
android:enabled="true"
- android:icon="@drawable/ic_people_black_24dp"/>
+ android:icon="@drawable/riot_tab_rooms"/>
+
+
diff --git a/vector/src/main/res/menu/vector_home_group_settings.xml b/vector/src/main/res/menu/vector_home_group_settings.xml
new file mode 100755
index 0000000000..e40c4b487f
--- /dev/null
+++ b/vector/src/main/res/menu/vector_home_group_settings.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/menu/vector_home_room_settings.xml b/vector/src/main/res/menu/vector_home_room_settings.xml
index 6dafba191b..2a1c282521 100755
--- a/vector/src/main/res/menu/vector_home_room_settings.xml
+++ b/vector/src/main/res/menu/vector_home_room_settings.xml
@@ -1,11 +1,27 @@
Rum navn
Rum emne
- settings_key_display_all_events
- settings_key_use_rage_shake
-
Opkald
Opkald forbundet
Opkald forbinder…
@@ -250,11 +247,6 @@ Beklager ulejligheden.