/* * 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 org.matrix.androidsdk.adapters; import android.content.Context; import android.graphics.Color; import android.graphics.Typeface; import android.text.TextUtils; import org.matrix.androidsdk.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.ImageView; import android.widget.TextView; import org.matrix.androidsdk.R; import org.matrix.androidsdk.data.Room; import org.matrix.androidsdk.data.RoomState; import org.matrix.androidsdk.data.RoomSummary; import org.matrix.androidsdk.rest.model.Event; import org.matrix.androidsdk.rest.model.PublicRoom; import org.matrix.androidsdk.util.EventDisplay; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; public abstract class RoomSummaryAdapter extends BaseExpandableListAdapter { private static final String LOG_TAG = "RoomSumAdapt"; protected Context mContext; private LayoutInflater mLayoutInflater; private int mLayoutResourceId; private int mHeaderLayoutResourceId; private int mUnreadColor; private int mHighlightColor; private int mPublicHighlightColor; private int mUnreadTextColor; private int mHighlightTextColor; private int mDefaultTextColor; private int mSectionTitleColor; private ArrayList<ArrayList<RoomSummary>> mRecentsSummariesList; protected List<List<PublicRoom>> mPublicRoomsLists = null; protected List<String> mPublicRoomsHomeServerLists = null; public int mPublicsGroupStartIndex = -1; private boolean mDisplayAllGroups = true; private ArrayList<ArrayList<RoomSummary>> mFilteredRecentsSummariesList = null; private ArrayList<ArrayList<PublicRoom>> mFilteredPublicRoomsList = null; private String mSearchedPattern = ""; private ArrayList<String> mHighLightedRooms = new ArrayList<>(); protected ArrayList<HashMap<String, RoomSummary>> mSummaryMapsBySection = new ArrayList<>(); // abstract methods public abstract int getUnreadMessageBackgroundColor(); public abstract int getHighlightMessageBackgroundColor(); public abstract int getPublicHighlightMessageBackgroundColor(); public abstract boolean displayPublicRooms(); public abstract String myRoomsTitle(int section); public abstract String publicRoomsTitle(int section); public abstract Room roomFromRoomSummary(RoomSummary roomSummary); public abstract String memberDisplayName(String matrixId, String userId); protected int getUnreadMessageTextColor() { return Color.BLACK; } protected int getHighlightMessageTextColor() { return Color.BLACK; } protected int getDefaultTextColor() { return Color.BLACK; } protected int getSectionTitleColor() { return Color.BLACK; } /** * Construct an adapter which will display a list of rooms. * @param context Activity context * @param layoutResourceId The resource ID of the layout for each item. Must have TextViews with * the IDs: roomsAdapter_roomName, roomsAdapter_roomTopic * @param headerLayoutResourceId the header layout id */ public RoomSummaryAdapter(Context context, int nbrSections, int layoutResourceId, int headerLayoutResourceId) { mContext = context; mLayoutResourceId = layoutResourceId; mHeaderLayoutResourceId = headerLayoutResourceId; mLayoutInflater = LayoutInflater.from(mContext); //setNotifyOnChange(false); mRecentsSummariesList = new ArrayList<>(); for(int section = 0; section < nbrSections; section++) { mRecentsSummariesList.add(new ArrayList<RoomSummary>()); mSummaryMapsBySection.add(new HashMap<String, RoomSummary>()); } mPublicRoomsLists = null; mUnreadColor = getUnreadMessageBackgroundColor(); mHighlightColor = getHighlightMessageBackgroundColor(); mPublicHighlightColor = getPublicHighlightMessageBackgroundColor(); mUnreadTextColor = getUnreadMessageTextColor(); mHighlightTextColor = getHighlightMessageTextColor(); mDefaultTextColor = getDefaultTextColor(); mSectionTitleColor = getSectionTitleColor(); } /** * search management */ public void setSearchedPattern(String pattern) { if (null == pattern) { pattern = ""; } if (!pattern.equals(mSearchedPattern)) { mSearchedPattern = pattern.toLowerCase(); this.notifyDataSetChanged(); } } @Override public void notifyDataSetChanged() { Log.d(LOG_TAG, "notifyDataSetChanged "); mFilteredRecentsSummariesList = new ArrayList<>(); mFilteredPublicRoomsList = new ArrayList<>(); // there is a pattern to search if (mSearchedPattern.length() > 0) { for(int index = 0; index < mRecentsSummariesList.size(); index++) { ArrayList<RoomSummary> roomSummaries = mRecentsSummariesList.get(index); ArrayList<RoomSummary> filteredRes = new ArrayList<>(); // search in the recent rooms for (RoomSummary summary : roomSummaries) { String roomName = summary.getRoomName(); if (!TextUtils.isEmpty(roomName) && (roomName.toLowerCase().contains(mSearchedPattern))) { filteredRes.add(summary); } else { String topic = summary.getRoomTopic(); if (!TextUtils.isEmpty(topic) && (topic.toLowerCase().contains(mSearchedPattern))) { filteredRes.add(summary); } } } mFilteredRecentsSummariesList.add(filteredRes); } // set to null until it is initialized if (null != mPublicRoomsLists) { for(List<PublicRoom> publicRoomslist : mPublicRoomsLists) { ArrayList<PublicRoom> fiteredList = new ArrayList<>(); mFilteredPublicRoomsList.add(fiteredList); for (PublicRoom publicRoom : publicRoomslist) { String roomName = publicRoom.name; if (!TextUtils.isEmpty(roomName) && (roomName.toLowerCase().contains(mSearchedPattern))) { fiteredList.add(publicRoom); } else { String alias = publicRoom.roomAliasName; if (!TextUtils.isEmpty(alias) && (alias.toLowerCase().contains(mSearchedPattern))) { fiteredList.add(publicRoom); } } } } } } super.notifyDataSetChanged(); } /** * Check if the group index is the recents one. * @param groupIndex the group index. * @return true if the recents group oone */ public boolean isRecentsGroupIndex(int groupIndex) { return (mPublicsGroupStartIndex < 0) || (groupIndex < mPublicsGroupStartIndex); } /** * Check if the group index is the public ones. * @param groupIndex the group index. * @return true if the group is the publics one. */ public boolean isPublicsGroupIndex(int groupIndex) { return (mPublicsGroupStartIndex > 0) && (groupIndex >= mPublicsGroupStartIndex); } /** * Force to display all the groups * @param displayAllGroups status */ public void setDisplayAllGroups(boolean displayAllGroups) { displayAllGroups |= displayPublicRooms(); if (mDisplayAllGroups != displayAllGroups) { mDisplayAllGroups = displayAllGroups; notifyDataSetChanged(); } } /** * public rooms list management */ public void setPublicRoomsList(List<List<PublicRoom>> aRoomsListList, List<String> homeServerNamesList) { mPublicRoomsLists = aRoomsListList; mPublicRoomsHomeServerLists = homeServerNamesList; if (null != aRoomsListList) { for(List<PublicRoom> publicRoomsList : mPublicRoomsLists) { // the public rooms must only be sorted once // sortSummaries is called at each new displayable event. Collections.sort(publicRoomsList, new Comparator<PublicRoom>() { @Override public int compare(PublicRoom publicRoom, PublicRoom publicRoom2) { return publicRoom2.numJoinedMembers - publicRoom.numJoinedMembers; } }); } } } /** * Returns the public Room at position (group, section) * @param groupIndex the group index. * @param section the section index. * @return the matched PublicRoom if it exists, else null. */ public PublicRoom getPublicRoomAt(int groupIndex, int section) { if (mSearchedPattern.length() > 0) { return mFilteredPublicRoomsList.get(groupIndex - mPublicsGroupStartIndex).get(section); } else { if (null != mPublicRoomsLists) { return mPublicRoomsLists.get(groupIndex - mPublicsGroupStartIndex).get(section); } return null; } } /** * Returns the home server URL for the group index. * @param groupIndex the group index. * @return the home server URL. */ public String getHomeServerURLAt(int groupIndex) { if (null != mPublicRoomsHomeServerLists) { return mPublicRoomsHomeServerLists.get(groupIndex - mPublicsGroupStartIndex); } return null; } /** * recent rooms list management */ public ArrayList<ArrayList<RoomSummary>> getRecentsSummariesList() { return mRecentsSummariesList; } public void addRoomSummary(int section, RoomSummary roomSummary) { if (section < mRecentsSummariesList.size()) { ArrayList<RoomSummary> list = mRecentsSummariesList.get(section); HashMap<String, RoomSummary> maps = mSummaryMapsBySection.get(section); // avoid multiple definitions if (maps.get(roomSummary.getRoomId()) == null) { list.add(roomSummary); maps.put(roomSummary.getRoomId(), roomSummary); } } } public RoomSummary getRoomSummaryAt(int section, int index) { if (mSearchedPattern.length() > 0) { return mFilteredRecentsSummariesList.get(section).get(index); } else { return mRecentsSummariesList.get(section).get(index); } } public void removeRoomSummary(int section, RoomSummary roomSummary) { mRecentsSummariesList.get(section).remove(roomSummary); if (null != roomSummary.getRoomId()) { mSummaryMapsBySection.get(section).remove(roomSummary.getRoomId()); } } public RoomSummary getSummaryByRoomId(int section, String roomId) { ArrayList<RoomSummary> list = mRecentsSummariesList.get(section); for (int i=0; i< list.size(); i++) { RoomSummary summary = list.get(i); if (roomId.equals(summary.getRoomId())) { return summary; } } return null; } public void removeSection(int section) { mRecentsSummariesList.remove(section); if ((null != mFilteredRecentsSummariesList) && (mFilteredRecentsSummariesList.size() > section)) { mFilteredRecentsSummariesList.remove(section); } mSummaryMapsBySection.remove(section); } /** * Set the latest event for a room summary. * @param event The latest event * @param roomState the roomState * @param refresh true to refresh the UI */ public void setLatestEvent(int section, Event event, RoomState roomState, boolean refresh) { RoomSummary summary = getSummaryByRoomId(section, event.roomId); if (summary != null) { summary.setLatestReceivedEvent(event); summary.setLatestRoomState(roomState); // refresh on demand if (refresh) { sortSummaries(); notifyDataSetChanged(); } } } /** * Defines that the room must be highlighted in the rooms list * @param roomId The room ID of the room to highlight. */ public void highlightRoom(String roomId) { if (mHighLightedRooms.indexOf(roomId) < 0) { mHighLightedRooms.add(roomId); } } /** * Reset the unread messages counter and remove the rooms from the highlighted rooms lists. * @param section the section * @param roomId the room Id. * @return true if there is an update. */ public boolean resetUnreadCount(int section, String roomId) { boolean res = false; RoomSummary roomSummary = mSummaryMapsBySection.get(section).get(roomId); // sanity check if (null != roomSummary) { Room room = roomFromRoomSummary(roomSummary); if (null != room) { room.sendReadReceipt(null); } res |= roomSummary.setHighlighted(false); } return res; } /** * Reset the unread message counters in a section. * @param section the section. * @return true if something has been updated. */ public boolean resetUnreadCounts(int section) { boolean res = false; Collection<RoomSummary> summaries = mSummaryMapsBySection.get(section).values(); for(RoomSummary summary : summaries) { Room room = roomFromRoomSummary(summary); if (null != room) { room.sendReadReceipt(null); } res |= summary.setHighlighted(false); } return res; } /** * Sort the room summaries list. * 1 - Sort by the latest event timestamp (most recent first). * 2 - Sort the public rooms by the number of members (bigger room first) */ public void sortSummaries() { for(int section = 0; section < mRecentsSummariesList.size(); section++) { ArrayList<RoomSummary> summariesList = mRecentsSummariesList.get(section); Collections.sort(summariesList, new Comparator<RoomSummary>() { @Override public int compare(RoomSummary lhs, RoomSummary rhs) { if (lhs == null || lhs.getLatestReceivedEvent() == null) { return 1; } else if (rhs == null || rhs.getLatestReceivedEvent() == null) { return -1; } if (lhs.getLatestReceivedEvent().getOriginServerTs() > rhs.getLatestReceivedEvent().getOriginServerTs()) { return -1; } else if (lhs.getLatestReceivedEvent().getOriginServerTs() < rhs.getLatestReceivedEvent().getOriginServerTs()) { return 1; } return 0; } }); } } /** * Provides the formatted timestamp to display. * null means that the timestamp text must be hidden. * @param event the event. * @return the formatted timestamp to display. */ protected String getFormattedTimestamp(Event event) { return event.formattedOriginServerTs(); } @Override public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { // display a spinner while loading the public rooms // detect if the view is progressbar_waiting_room_members one View spinner = null; if (null != convertView) { spinner = convertView.findViewById(R.id.progressbar_waiting_room_members); } // assume that some public rooms are defined if (isPublicsGroupIndex(groupPosition) && (null == mPublicRoomsLists)) { if (null == spinner) { convertView = mLayoutInflater.inflate(R.layout.adapter_item_waiting_room_members, parent, false); } return convertView; } // must not reuse the view if it is not the right type if (null != spinner) { convertView = null; } if (convertView == null) { convertView = mLayoutInflater.inflate(mLayoutResourceId, parent, false); } try { // default UI // when a room is deleting, the UI is dimmed final View deleteProgress = convertView.findViewById(R.id.roomSummaryAdapter_delete_progress); deleteProgress.setVisibility(View.GONE); convertView.setAlpha(1.0f); int textColor = mDefaultTextColor; if (isRecentsGroupIndex(groupPosition)) { List<RoomSummary> summariesList = (mSearchedPattern.length() > 0) ? mFilteredRecentsSummariesList.get(groupPosition) : mRecentsSummariesList.get(groupPosition); // should never happen but in some races conditions, it happened. if (0 == summariesList.size()) { return convertView; } RoomSummary summary = (childPosition < summariesList.size()) ? summariesList.get(childPosition) : summariesList.get(summariesList.size() - 1); Integer unreadCount = summary.getUnreadEventsCount(); CharSequence message = summary.getRoomTopic(); String timestamp = null; // background color if (summary.isHighlighted()) { convertView.setBackgroundColor(mHighlightColor); textColor = mHighlightTextColor; } else if ((unreadCount == null) || (unreadCount == 0)) { convertView.setBackgroundColor(0); } else { convertView.setBackgroundColor(mUnreadColor); textColor = mUnreadTextColor; } TextView textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_roomName); RoomState latestRoomState = summary.getLatestRoomState(); if (null == latestRoomState) { Room room = roomFromRoomSummary(summary); if ((null != room) && (null != room.getState())) { latestRoomState = room.getState().deepCopy(); // store it to avoid retrieving it once summary.setLatestRoomState(latestRoomState); } } // the public rooms are displayed with bold fonts if ((null != latestRoomState) && (null != latestRoomState.visibility) && latestRoomState.visibility.equals(RoomState.DIRECTORY_VISIBILITY_PUBLIC)) { textView.setTypeface(null, Typeface.BOLD); } else { textView.setTypeface(null, Typeface.NORMAL); } textView.setTextColor(textColor); // display the unread messages count String roomNameMessage = ((latestRoomState != null) && !summary.isInvited()) ? latestRoomState.getDisplayName(summary.getMatrixId()) : summary.getRoomName(); if (null != roomNameMessage) { if ((null != unreadCount) && (unreadCount > 0) && !summary.isInvited()) { roomNameMessage += " (" + unreadCount + ")"; } } textView.setText(roomNameMessage); if (summary.getLatestReceivedEvent() != null) { EventDisplay display = new EventDisplay(mContext, summary.getLatestReceivedEvent(), latestRoomState); display.setPrependMessagesWithAuthor(true); message = display.getTextualDisplay(); timestamp = getFormattedTimestamp(summary.getLatestReceivedEvent()); } // check if this is an invite if (summary.isInvited() && (null != summary.getInviterUserId())) { String inviterName = summary.getInviterUserId(); String myName = summary.getMatrixId(); if (null != latestRoomState) { inviterName = latestRoomState.getMemberName(inviterName); myName = latestRoomState.getMemberName(myName); } else { inviterName = memberDisplayName(summary.getMatrixId(), inviterName); myName = memberDisplayName(summary.getMatrixId(), myName); } message = mContext.getString(R.string.notice_room_invite, inviterName, myName); } textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_message); textView.setText(message); textView.setTextColor(textColor); textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_ts); textView.setVisibility(View.VISIBLE); textView.setText(timestamp); textView.setTextColor(textColor); Room room = roomFromRoomSummary(summary); if ((null != room) && room.isLeaving()) { convertView.setAlpha(0.3f); deleteProgress.setVisibility(View.VISIBLE); } } else { int index = groupPosition - mPublicsGroupStartIndex; List<PublicRoom> publicRoomsList = null; if (mSearchedPattern.length() > 0) { // add sanity checks // GA issue : could crash while rotating the screen if ((null != mFilteredPublicRoomsList) && (index < mFilteredPublicRoomsList.size())) { publicRoomsList = mFilteredPublicRoomsList.get(index); } } else { // add sanity checks // GA issue : could crash while rotating the screen if ((null != mPublicRoomsLists) && (index < mPublicRoomsLists.size())) { publicRoomsList = mPublicRoomsLists.get(index); } } // sanity checks failed. if (null == publicRoomsList) { TextView textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_roomName); textView.setTypeface(null, Typeface.BOLD); textView.setTextColor(textColor); textView.setText(""); textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_message); textView.setTextColor(textColor); textView.setText(""); textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_ts); textView.setTextColor(textColor); textView.setVisibility(View.VISIBLE); textView.setText(""); convertView.setBackgroundColor(0); } else { PublicRoom publicRoom = publicRoomsList.get(childPosition); String matrixId = null; if ((mRecentsSummariesList.size() > 0) && (mRecentsSummariesList.get(0).size() > 0)) { matrixId = mRecentsSummariesList.get(0).get(0).getMatrixId(); } String displayName = publicRoom.getDisplayName(matrixId); TextView textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_roomName); textView.setTypeface(null, Typeface.BOLD); textView.setTextColor(textColor); textView.setText(displayName); textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_message); textView.setText(publicRoom.topic); textView.setTextColor(textColor); textView = (TextView) convertView.findViewById(R.id.roomSummaryAdapter_ts); textView.setVisibility(View.VISIBLE); textView.setTextColor(textColor); if (publicRoom.numJoinedMembers > 1) { textView.setText(publicRoom.numJoinedMembers + " " + mContext.getString(R.string.users)); } else { textView.setText(publicRoom.numJoinedMembers + " " + mContext.getString(R.string.user)); } String alias = publicRoom.getAlias(); if ((null != alias) && (mHighLightedRooms.indexOf(alias) >= 0)) { convertView.setBackgroundColor(mPublicHighlightColor); } else { convertView.setBackgroundColor(0); } } } } catch (Exception e) { // prefer having a weird UI instead of a crash } return convertView; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(mHeaderLayoutResourceId, null); } TextView heading = (TextView) convertView.findViewById(R.id.heading); if (isRecentsGroupIndex(groupPosition)) { int unreadCount = 0; Collection<RoomSummary> summaries = mSummaryMapsBySection.get(groupPosition).values(); for(RoomSummary summary : summaries) { unreadCount += summary.getUnreadEventsCount(); } String header = myRoomsTitle(groupPosition); if (unreadCount > 0) { header += " (" + unreadCount + ")"; } heading.setText(header); } else { heading.setText(publicRoomsTitle(groupPosition)); } heading.setTextColor(mSectionTitleColor); ImageView imageView = (ImageView) convertView.findViewById(R.id.heading_image); if (isExpanded) { imageView.setImageResource(R.drawable.expander_close_holo_light); } else { imageView.setImageResource(R.drawable.expander_open_holo_light); } return convertView; } @Override public Object getChild(int groupPosition, int childPosition) { return null; } @Override public long getChildId(int groupPosition, int childPosition) { return 0; } @Override public int getChildrenCount(int groupPosition) { if (isRecentsGroupIndex(groupPosition)) { ArrayList<ArrayList<RoomSummary>> list = (mSearchedPattern.length() > 0) ? mFilteredRecentsSummariesList : mRecentsSummariesList; if ((null == list) || (list.size() <= groupPosition)) { return 0; } else { return list.get(groupPosition).size(); } } else { int index = groupPosition - mPublicsGroupStartIndex; if (!displayPublicRooms()) { return 0; } // display a spinner until the public rooms are loaded // else if (null == mPublicRoomsLists) { return 1; } else if (mPublicRoomsLists.get(index).size() == 0) { return 0; } else { return (mSearchedPattern.length() > 0) ? mFilteredPublicRoomsList.get(index).size() : mPublicRoomsLists.get(index).size(); } } } @Override public Object getGroup(int groupPosition) { return null; } @Override public int getGroupCount() { int count = 0; mPublicsGroupStartIndex = -1; count += mRecentsSummariesList.size(); // display the public rooms in the recents only if there is no dedicated room if ((mRecentsSummariesList.size() == 0) || mDisplayAllGroups) { mPublicsGroupStartIndex = count; if (null == mPublicRoomsLists) { count++; } else { count += mPublicRoomsLists.size(); } } return count; } @Override public void onGroupCollapsed(int groupPosition) { super.onGroupCollapsed(groupPosition); } @Override public void onGroupExpanded(int groupPosition) { super.onGroupExpanded(groupPosition); } @Override public long getGroupId(int groupPosition) { return 0; } @Override public boolean hasStableIds() { return false; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } }