package co.tinode.tindroid; import android.Manifest; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; import java.util.Collection; import java.util.HashMap; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.AppCompatImageView; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import co.tinode.tindroid.account.ContactsManager; import co.tinode.tindroid.db.StoredSubscription; import co.tinode.tindroid.media.VxCard; import co.tinode.tindroid.widgets.LetterTileDrawable; import co.tinode.tindroid.widgets.RoundImageDrawable; import co.tinode.tinodesdk.ComTopic; import co.tinode.tinodesdk.NotConnectedException; import co.tinode.tinodesdk.PromisedReply; import co.tinode.tinodesdk.Tinode; import co.tinode.tinodesdk.Topic; import co.tinode.tinodesdk.model.Acs; import co.tinode.tinodesdk.model.Drafty; import co.tinode.tinodesdk.model.PrivateType; import co.tinode.tinodesdk.model.ServerMessage; import co.tinode.tinodesdk.model.Subscription; import static android.app.Activity.RESULT_OK; /** * Topic Info fragment: p2p or a group topic. */ public class TopicInfoFragment extends Fragment { private static final String TAG = "TopicInfoFragment"; private static final int ACTION_DELETE = 1; private static final int ACTION_LEAVE = 2; private static final int ACTION_REPORT = 3; private static final int ACTION_REMOVE = 4; private static final int ACTION_BAN_TOPIC = 5; private static final int ACTION_BAN_MEMBER = 6; private static final int ACTION_DELMSG = 7; private ComTopic<VxCard> mTopic; private MembersAdapter mMembersAdapter; private PromisedReply.FailureListener<ServerMessage> mFailureListener; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_topic_info, container, false); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public void onViewCreated(@NonNull View view, Bundle savedInstance) { final Activity activity = getActivity(); if (activity == null) { return; } mMembersAdapter = new MembersAdapter(); mFailureListener = new UiUtils.ToastFailureListener(activity); RecyclerView rv = view.findViewById(R.id.groupMembers); rv.setLayoutManager(new LinearLayoutManager(activity, RecyclerView.VERTICAL, false)); rv.addItemDecoration(new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)); rv.setAdapter(mMembersAdapter); rv.setNestedScrollingEnabled(false); // Set up listeners view.findViewById(R.id.uploadAvatar).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { UiUtils.requestAvatar(TopicInfoFragment.this); } }); final Switch muted = view.findViewById(R.id.switchMuted); muted.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) { mTopic.updateMuted(isChecked).thenCatch(new PromisedReply.FailureListener<ServerMessage>() { @Override public <E extends Exception> PromisedReply<ServerMessage> onFailure(E err) { activity.runOnUiThread(new Runnable() { @Override public void run() { muted.setChecked(!isChecked); } }); if (err instanceof NotConnectedException) { Toast.makeText(activity, R.string.no_connection, Toast.LENGTH_SHORT).show(); } return null; } }); } }); final Switch archived = view.findViewById(R.id.switchArchived); archived.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) { mTopic.updateArchived(isChecked).thenCatch(new PromisedReply.FailureListener<ServerMessage>() { @Override public <E extends Exception> PromisedReply<ServerMessage> onFailure(E err) { activity.runOnUiThread(new Runnable() { @Override public void run() { archived.setChecked(!isChecked); } }); if (err instanceof NotConnectedException) { Toast.makeText(activity, R.string.no_connection, Toast.LENGTH_SHORT).show(); } return null; } }); } }); view.findViewById(R.id.permissionsSingle).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { UiUtils.showEditPermissions(activity, mTopic, mTopic.getAccessMode().getWant(), null, UiUtils.ACTION_UPDATE_SELF_SUB, mTopic.isP2PType() ? "OASD" : "O"); } }); view.findViewById(R.id.permissions).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ((MessageActivity) activity).showFragment(MessageActivity.FRAGMENT_PERMISSIONS, null, true); } }); view.findViewById(R.id.buttonClearMessages).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int confirm = mTopic.isDeleter() ? R.string.confirm_delmsg_for_all : R.string.confirm_delmsg_for_self; showConfirmationDialog(null, null, null, R.string.clear_messages, confirm, ACTION_DELMSG); } }); view.findViewById(R.id.buttonLeave).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showConfirmationDialog(null, null, null, R.string.leave_conversation, R.string.confirm_leave_topic, ACTION_LEAVE); } }); view.findViewById(R.id.buttonDeleteGroup).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showConfirmationDialog(null, null, null, R.string.delete_group, R.string.confirm_delete_topic, ACTION_DELETE); } }); view.findViewById(R.id.buttonBlock).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { VxCard pub = mTopic.getPub(); String topicTitle = pub != null ? pub.fn : null; topicTitle = TextUtils.isEmpty(topicTitle) ? activity.getString(R.string.placeholder_topic_title) : topicTitle; showConfirmationDialog(topicTitle, null, null, R.string.block_contact, R.string.confirm_contact_ban, ACTION_BAN_TOPIC); } }); final View.OnClickListener reportListener = new View.OnClickListener() { @Override public void onClick(View view) { VxCard pub = mTopic.getPub(); String topicTitle = pub != null ? pub.fn : null; topicTitle = TextUtils.isEmpty(topicTitle) ? activity.getString(R.string.placeholder_topic_title) : topicTitle; showConfirmationDialog(topicTitle, null, null, R.string.block_and_report, R.string.confirm_report, ACTION_REPORT); } }; view.findViewById(R.id.buttonReportContact).setOnClickListener(reportListener); view.findViewById(R.id.buttonReportGroup).setOnClickListener(reportListener); view.findViewById(R.id.buttonAddMembers).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ((MessageActivity) activity).showFragment(MessageActivity.FRAGMENT_EDIT_MEMBERS, null, true); } }); } @Override @SuppressWarnings("unchecked") // onResume sets up the form with values and views which do not change + sets up listeners. public void onResume() { super.onResume(); final Activity activity = getActivity(); final Bundle args = getArguments(); if (activity == null || args == null) { return; } String name = args.getString("topic"); mTopic = (ComTopic<VxCard>) Cache.getTinode().getTopic(name); if (mTopic == null) { Log.d(TAG, "TopicInfo resumed with null topic."); activity.finish(); return; } final TextView title = activity.findViewById(R.id.topicTitle); final TextView subtitle = activity.findViewById(R.id.topicSubtitle); final TextView address = activity.findViewById(R.id.topicAddress); final View uploadAvatarButton = activity.findViewById(R.id.uploadAvatar); final View permissions = activity.findViewById(R.id.permissions); final View permissionsSingle = activity.findViewById(R.id.singleUserPermissions); final View groupMembers = activity.findViewById(R.id.groupMembersWrapper); final View deleteGroup = activity.findViewById(R.id.buttonDeleteGroup); final View blockContact = activity.findViewById(R.id.buttonBlock); final View reportGroup = activity.findViewById(R.id.buttonReportGroup); final View reportContact = activity.findViewById(R.id.buttonReportContact); // Launch edit dialog when title or subtitle is clicked. final View.OnClickListener l = new View.OnClickListener() { @Override public void onClick(View v) { showEditTopicText(); } }; if (mTopic.isOwner()) { title.setOnClickListener(l); title.setBackgroundResource(R.drawable.dotted_line); } else { title.setBackgroundResource(0); } subtitle.setOnClickListener(l); address.setText(mTopic.getName()); if (mTopic.isGrpType()) { // Group topic uploadAvatarButton.setVisibility(mTopic.isManager() ? View.VISIBLE : View.GONE); groupMembers.setVisibility(View.VISIBLE); reportContact.setVisibility(View.GONE); View buttonLeave = activity.findViewById(R.id.buttonLeave); if (mTopic.isOwner()) { permissions.setVisibility(View.VISIBLE); permissionsSingle.setVisibility(View.GONE); buttonLeave.setVisibility(View.GONE); reportGroup.setVisibility(View.GONE); blockContact.setVisibility(View.GONE); deleteGroup.setVisibility(View.VISIBLE); } else { permissions.setVisibility(View.GONE); permissionsSingle.setVisibility(View.VISIBLE); buttonLeave.setVisibility(View.VISIBLE); reportGroup.setVisibility(View.VISIBLE); blockContact.setVisibility(View.VISIBLE); deleteGroup.setVisibility(View.GONE); } Button button = activity.findViewById(R.id.buttonAddMembers); if (!mTopic.isSharer() && !mTopic.isManager()) { // FIXME: allow sharers to add members but not remove. // Disable and gray out "invite members" button because only admins can // invite group members. button.setEnabled(false); button.setAlpha(0.5f); } else { button.setEnabled(true); button.setAlpha(1f); } } else { // P2P topic uploadAvatarButton.setVisibility(View.GONE); groupMembers.setVisibility(View.GONE); permissions.setVisibility(View.GONE); permissionsSingle.setVisibility(View.VISIBLE); deleteGroup.setVisibility(View.GONE); reportGroup.setVisibility(View.GONE); reportContact.setVisibility(View.VISIBLE); blockContact.setVisibility(View.VISIBLE); } notifyContentChanged(); notifyDataSetChanged(); } // Dialog for editing pub.fn and priv private void showEditTopicText() { final Activity activity = getActivity(); if (activity == null) { return; } VxCard pub = mTopic.getPub(); final String title = pub == null ? null : pub.fn; final PrivateType priv = mTopic.getPriv(); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); final View editor = View.inflate(builder.getContext(), R.layout.dialog_edit_group, null); builder.setView(editor).setTitle(R.string.edit_topic); final EditText titleEditor = editor.findViewById(R.id.editTitle); final EditText subtitleEditor = editor.findViewById(R.id.editPrivate); if (mTopic.isOwner()) { if (!TextUtils.isEmpty(title)) { titleEditor.setText(title); titleEditor.setSelection(title.length()); } } else { editor.findViewById(R.id.editTitleWrapper).setVisibility(View.GONE); } if (priv != null && !TextUtils.isEmpty(priv.getComment())) { subtitleEditor.setText(priv.getComment()); } builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String newTitle = null; if (mTopic.isOwner()) { newTitle = titleEditor.getText().toString().trim(); } String newPriv = subtitleEditor.getText().toString().trim(); UiUtils.updateTitle(activity, mTopic, newTitle, newPriv, new UiUtils.TitleUpdateCallbackInterface() { @Override public void onTitleUpdated() { activity.runOnUiThread(new Runnable() { @Override public void run() { notifyContentChanged(); } }); } }); } }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); } // Confirmation dialog "Do you really want to do X?" // uid - user to apply action to // message_id - id of the string resource to use as an explanation. // what - action to take on success, ACTION_* private void showConfirmationDialog(final String arg1, final String arg2, final String uid, int title_id, int message_id, final int what) { final Activity activity = getActivity(); if (activity == null) { return; } final AlertDialog.Builder confirmBuilder = new AlertDialog.Builder(activity); confirmBuilder.setNegativeButton(android.R.string.no, null); if (title_id != 0) { confirmBuilder.setTitle(title_id); } String message = activity.getString(message_id, arg1, arg2); confirmBuilder.setMessage(message); confirmBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { PromisedReply<ServerMessage> response = null; switch (what) { case ACTION_LEAVE: response = mTopic.delete(true); break; case ACTION_REPORT: HashMap<String, Object> json = new HashMap<>(); json.put("action", "report"); json.put("target", mTopic.getName()); Drafty msg = new Drafty().attachJSON(json); Cache.getTinode().publish(Tinode.TOPIC_SYS, msg, Tinode.draftyHeadersFor(msg)); response = mTopic.updateMode(null, "-JP"); break; case ACTION_REMOVE: response = mTopic.eject(uid, false); break; case ACTION_BAN_TOPIC: response = mTopic.updateMode(null, "-JP"); break; case ACTION_BAN_MEMBER: response = mTopic.eject(uid, true); break; case ACTION_DELMSG: response = mTopic.delMessages(true); } if (response != null) { response.thenApply(new PromisedReply.SuccessListener<ServerMessage>() { @Override public PromisedReply<ServerMessage> onSuccess(ServerMessage result) { Intent intent = new Intent(activity, ChatsActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); activity.finish(); return null; } }).thenCatch(mFailureListener); } } }); confirmBuilder.show(); } // Dialog-menu with actions for individual subscribers, like "send message", "change permissions", "ban", etc. private void showMemberAction(final String topicTitle, final String userTitle, final String uid, final String mode) { final Activity activity = getActivity(); if (activity == null) { return; } if (Cache.getTinode().isMe(uid)) { return; } final String userTitleFixed = TextUtils.isEmpty(userTitle) ? activity.getString(R.string.placeholder_contact_title) : userTitle; final String topicTitleFixed = TextUtils.isEmpty(topicTitle) ? activity.getString(R.string.placeholder_topic_title) : topicTitle; AlertDialog.Builder actionBuilder = new AlertDialog.Builder(activity); final LinearLayout actions = (LinearLayout) View.inflate(activity, R.layout.dialog_member_actions, null); actionBuilder .setTitle(TextUtils.isEmpty(userTitle) ? activity.getString(R.string.placeholder_contact_title) : userTitle) .setView(actions) .setCancelable(true) .setNegativeButton(android.R.string.cancel, null); final AlertDialog dialog = actionBuilder.create(); View.OnClickListener ocl = new View.OnClickListener() { @Override public void onClick(View v) { try { Intent intent; switch (v.getId()) { case R.id.buttonViewProfile: if (UiUtils.isPermissionGranted(activity, Manifest.permission.READ_CONTACTS)) { // This requires READ_CONTACTS permission String lookupKey = ContactsManager.getLookupKey(activity.getContentResolver(), uid); intent = new Intent(Intent.ACTION_VIEW, Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)); if (intent.resolveActivity(activity.getPackageManager()) != null) { startActivity(intent); } } else { Log.i(TAG, "Missing READ_CONTACTS permissions"); requestPermissions(new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS}, UiUtils.CONTACTS_PERMISSION_ID); Toast.makeText(activity, R.string.some_permissions_missing, Toast.LENGTH_SHORT).show(); } break; case R.id.buttonSendMessage: intent = new Intent(activity, MessageActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); intent.putExtra("topic", uid); startActivity(intent); break; case R.id.buttonPermissions: UiUtils.showEditPermissions(activity, mTopic, mode, uid, UiUtils.ACTION_UPDATE_SUB, "O"); break; case R.id.buttonMakeOwner: mTopic.updateMode(uid, "+O").thenApply(null, mFailureListener); break; case R.id.buttonRemove: { showConfirmationDialog(userTitleFixed, topicTitleFixed, uid, R.string.remove_from_group, R.string.confirm_member_removal, ACTION_REMOVE); break; } case R.id.buttonBlock: { showConfirmationDialog(userTitleFixed, topicTitleFixed, uid, R.string.block, R.string.confirm_member_ban, ACTION_BAN_MEMBER); break; } } } catch (NotConnectedException ignored) { Toast.makeText(activity, R.string.no_connection, Toast.LENGTH_SHORT).show(); } catch (Exception ignored) { Toast.makeText(activity, R.string.action_failed, Toast.LENGTH_SHORT).show(); } dialog.dismiss(); } }; actions.findViewById(R.id.buttonViewProfile).setOnClickListener(ocl); actions.findViewById(R.id.buttonSendMessage).setOnClickListener(ocl); if (mTopic.isOwner()) { actions.findViewById(R.id.buttonMakeOwner).setOnClickListener(ocl); } else { actions.findViewById(R.id.buttonMakeOwner).setVisibility(View.GONE); } if (mTopic.isAdmin() || mTopic.isOwner()) { actions.findViewById(R.id.buttonPermissions).setOnClickListener(ocl); actions.findViewById(R.id.buttonRemove).setOnClickListener(ocl); actions.findViewById(R.id.buttonBlock).setOnClickListener(ocl); } else { actions.findViewById(R.id.buttonPermissions).setVisibility(View.GONE); actions.findViewById(R.id.buttonRemove).setVisibility(View.GONE); actions.findViewById(R.id.buttonBlock).setVisibility(View.GONE); } dialog.show(); } void notifyDataSetChanged() { final Activity activity = getActivity(); if (activity == null) { return; } notifyContentChanged(); if (mTopic.isGrpType()) { mMembersAdapter.resetContent(); } } // Called when topic description is changed. private void notifyContentChanged() { final Activity activity = getActivity(); if (activity == null || activity.isFinishing() || activity.isDestroyed()) { return; } final AppCompatImageView avatar = activity.findViewById(R.id.imageAvatar); final TextView title = activity.findViewById(R.id.topicTitle); final TextView subtitle = activity.findViewById(R.id.topicSubtitle); VxCard pub = mTopic.getPub(); if (pub != null && !TextUtils.isEmpty(pub.fn)) { title.setText(pub.fn); title.setTypeface(null, Typeface.NORMAL); title.setTextIsSelectable(true); } else { title.setText(R.string.placeholder_contact_title); title.setTypeface(null, Typeface.ITALIC); title.setTextIsSelectable(false); } final Bitmap bmp = pub != null ? pub.getBitmap() : null; if (bmp != null) { avatar.setImageDrawable(new RoundImageDrawable(getResources(), bmp)); } else { avatar.setImageDrawable( new LetterTileDrawable(requireContext()) .setIsCircular(true) .setContactTypeAndColor( mTopic.getTopicType() == Topic.TopicType.P2P ? LetterTileDrawable.ContactType.PERSON : LetterTileDrawable.ContactType.GROUP) .setLetterAndColor(pub != null ? pub.fn : null, mTopic.getName())); } PrivateType priv = mTopic.getPriv(); if (priv != null && !TextUtils.isEmpty(priv.getComment())) { subtitle.setText(priv.getComment()); subtitle.setTypeface(null, Typeface.NORMAL); TypedValue typedValue = new TypedValue(); Resources.Theme theme = getActivity().getTheme(); theme.resolveAttribute(android.R.attr.textColorSecondary, typedValue, true); TypedArray arr = activity.obtainStyledAttributes(typedValue.data, new int[]{android.R.attr.textColorSecondary}); subtitle.setTextColor(arr.getColor(0, -1)); arr.recycle(); subtitle.setTextIsSelectable(true); } else { subtitle.setText(R.string.placeholder_private); subtitle.setTypeface(null, Typeface.ITALIC); subtitle.setTextColor(getResources().getColor(R.color.colorTextPlaceholder)); subtitle.setTextIsSelectable(false); } ((Switch) activity.findViewById(R.id.switchMuted)).setChecked(mTopic.isMuted()); ((Switch) activity.findViewById(R.id.switchArchived)).setChecked(mTopic.isArchived()); Acs acs = mTopic.getAccessMode(); ((TextView) activity.findViewById(R.id.permissionsSingle)).setText(acs == null ? "" : acs.getMode()); } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { // Execution has to be delayed because the topic is not yet subscribed: // The image selection activity was on top while MessageActivity was paused. It just got // unpaused and did not have time to re-subscribe. if (requestCode == UiUtils.ACTIVITY_RESULT_SELECT_PICTURE && resultCode == RESULT_OK) { final MessageActivity activity = (MessageActivity) getActivity(); if (activity != null) { activity.submitForExecution(new Runnable() { @Override public void run() { UiUtils.updateAvatar(activity, mTopic, data); } }); } } } @Override public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { // Inflate the menu; this adds items to the action bar if it is present. // inflater.inflate(R.menu.menu_topic_info, menu); super.onCreateOptionsMenu(menu, inflater); } private static class MemberViewHolder extends RecyclerView.ViewHolder { TextView name; TextView extraInfo; LinearLayout statusContainer; TextView[] status; ImageButton more; AppCompatImageView icon; MemberViewHolder(View item) { super(item); name = item.findViewById(android.R.id.text1); extraInfo = item.findViewById(android.R.id.text2); statusContainer = item.findViewById(R.id.statusContainer); status = new TextView[statusContainer.getChildCount()]; for (int i = 0; i < status.length; i++) { status[i] = (TextView) statusContainer.getChildAt(i); } more = item.findViewById(R.id.optionsMenu); icon = item.findViewById(android.R.id.icon); } } private class MembersAdapter extends RecyclerView.Adapter<MemberViewHolder> { private Subscription<VxCard,PrivateType>[] mItems; private int mItemCount; @SuppressWarnings("unchecked") MembersAdapter() { mItems = (Subscription<VxCard,PrivateType>[]) new Subscription[8]; mItemCount = 0; } /** * Must be run on UI thread */ void resetContent() { if (mTopic != null) { Collection<Subscription<VxCard,PrivateType>> c = mTopic.getSubscriptions(); if (c != null) { mItemCount = c.size(); mItems = c.toArray(mItems); } else { mItemCount = 0; } notifyDataSetChanged(); } } @Override public int getItemCount() { return mItemCount; } @Override public long getItemId(int i) { return StoredSubscription.getId(mItems[i]); } @NonNull @Override public MemberViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // create a new view View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.group_member, parent, false); return new MemberViewHolder(v); } @Override public void onBindViewHolder(@NonNull final MemberViewHolder holder, int position) { final Activity activity = getActivity(); if (activity == null) { return; } final Subscription<VxCard,PrivateType> sub = mItems[position]; final StoredSubscription ss = (StoredSubscription) sub.getLocal(); final boolean isMe = Cache.getTinode().isMe(sub.user); Bitmap bmp = null; String title = isMe ? activity.getString(R.string.current_user) : null; if (sub.pub != null) { if (title == null) { title = !TextUtils.isEmpty(sub.pub.fn) ? sub.pub.fn : activity.getString(R.string.placeholder_contact_title); } bmp = sub.pub.getBitmap(); } else { Log.w(TAG, "Pub is null for " + sub.user); } holder.name.setText(title); holder.extraInfo.setText(sub.acs != null ? sub.acs.getMode() : ""); int i = 0; UiUtils.AccessModeLabel[] labels = UiUtils.accessModeLabels(sub.acs, ss.status); if (labels != null) { for (UiUtils.AccessModeLabel l : labels) { holder.status[i].setText(l.nameId); holder.status[i].setTextColor(l.color); ((GradientDrawable) holder.status[i].getBackground()).setStroke(2, l.color); holder.status[i++].setVisibility(View.VISIBLE); } } for (; i < holder.status.length; i++) { holder.status[i].setVisibility(View.GONE); } holder.icon.setImageDrawable(UiUtils.avatarDrawable(activity, bmp, sub.pub != null ? sub.pub.fn : null, sub.user)); final View.OnClickListener action = new View.OnClickListener() { @Override public void onClick(View v) { int position = holder.getAdapterPosition(); final Subscription<VxCard,PrivateType> sub = mItems[position]; VxCard pub = mTopic.getPub(); showMemberAction(pub != null ? pub.fn : null, holder.name.getText().toString(), sub.user, sub.acs.getGiven()); } }; holder.itemView.setOnClickListener(action); if (isMe) { holder.more.setVisibility(View.INVISIBLE); } else { holder.more.setVisibility(View.VISIBLE); holder.more.setOnClickListener(action); } } } }