Group member dialog update.

fork-5.53.8
Alan Evans 2020-03-03 18:34:55 -04:00 zatwierdzone przez Greyson Parrelli
rodzic d05a71c8fe
commit 28bbfd88b2
10 zmienionych plików z 421 dodań i 114 usunięć

Wyświetl plik

@ -1,131 +1,78 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.Lifecycle;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
public final class GroupMembersDialog {
private static final String TAG = GroupMembersDialog.class.getSimpleName();
private final Context context;
private final Recipient groupRecipient;
private final Lifecycle lifecycle;
private final Recipient recipient;
private final Context context;
public GroupMembersDialog(Context context, Recipient recipient) {
this.recipient = recipient;
this.context = context;
}
@Override
public void onPreExecute() {}
@Override
protected List<Recipient> doInBackground(Void... params) {
return DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), true);
}
@Override
public void onPostExecute(List<Recipient> members) {
GroupMembers groupMembers = new GroupMembers(members);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationActivity_group_members);
builder.setIconAttribute(R.attr.group_members_dialog_icon);
builder.setCancelable(true);
builder.setItems(groupMembers.getRecipientStrings(), new GroupMembersOnClickListener(context, groupMembers));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
public GroupMembersDialog(@NonNull Context context,
@NonNull Recipient groupRecipient,
@NonNull Lifecycle lifecycle)
{
this.context = context;
this.groupRecipient = groupRecipient;
this.lifecycle = lifecycle;
}
public void display() {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
SimpleTask.run(
lifecycle,
() -> DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupRecipient.requireGroupId(), true),
members -> {
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.ConversationActivity_group_members)
.setIconAttribute(R.attr.group_members_dialog_icon)
.setCancelable(true)
.setView(R.layout.dialog_group_members)
.setPositiveButton(android.R.string.ok, null)
.show();
GroupMemberListView memberListView = dialog.findViewById(R.id.list_members);
ArrayList<GroupMemberEntry.FullMember> pendingMembers = new ArrayList<>(members.size());
for (Recipient member : members) {
GroupMemberEntry.FullMember entry = new GroupMemberEntry.FullMember(member);
entry.setOnClick(() -> contactClick(member));
if (member.isLocalNumber()) {
pendingMembers.add(0, entry);
} else {
pendingMembers.add(entry);
}
}
//noinspection ConstantConditions
memberListView.setMembers(pendingMembers);
}
);
}
private static class GroupMembersOnClickListener implements DialogInterface.OnClickListener {
private final GroupMembers groupMembers;
private final Context context;
private void contactClick(@NonNull Recipient recipient) {
if (recipient.getContactUri() != null) {
Intent intent = new Intent(context, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
public GroupMembersOnClickListener(Context context, GroupMembers members) {
this.context = context;
this.groupMembers = members;
}
@Override
public void onClick(DialogInterface dialogInterface, int item) {
Recipient recipient = groupMembers.get(item);
if (recipient.getContactUri() != null) {
Intent intent = new Intent(context, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
context.startActivity(intent);
} else {
context.startActivity(RecipientExporter.export(recipient).asAddContactIntent());
}
}
}
/**
* Wraps a List of Recipient (just like @class Recipients),
* but with focus on the order of the Recipients.
* So that the order of the RecipientStrings[] matches
* the internal order.
*
* @author Christoph Haefner
*/
private class GroupMembers {
private final String TAG = GroupMembers.class.getSimpleName();
private final LinkedList<Recipient> members = new LinkedList<>();
public GroupMembers(List<Recipient> recipients) {
for (Recipient recipient : recipients) {
if (recipient.isLocalNumber()) {
members.push(recipient);
} else {
members.add(recipient);
}
}
}
public String[] getRecipientStrings() {
List<String> recipientStrings = new LinkedList<>();
for (Recipient recipient : members) {
if (recipient.isLocalNumber()) {
recipientStrings.add(context.getString(R.string.GroupMembersDialog_you));
} else {
String name = getRecipientName(recipient);
recipientStrings.add(name);
}
}
return recipientStrings.toArray(new String[members.size()]);
}
private String getRecipientName(Recipient recipient) {
if (FeatureFlags.profileDisplay()) return recipient.getDisplayName(context);
String name = recipient.toShortString(context);
if (recipient.getName(context) == null && !recipient.getProfileName().isEmpty()) {
name += " ~" + recipient.getProfileName().toString();
}
return name;
}
public Recipient get(int index) {
return members.get(index);
context.startActivity(intent);
} else {
context.startActivity(RecipientExporter.export(recipient).asAddContactIntent());
}
}
}

Wyświetl plik

@ -21,9 +21,11 @@ import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.Objects;
@ -109,6 +111,15 @@ public final class AvatarImageView extends AppCompatImageView {
this.fallbackPhotoProvider = fallbackPhotoProvider;
}
public void setRecipient(@NonNull Recipient recipient) {
if (recipient.isLocalNumber()) {
setAvatar(GlideApp.with(this), null, false);
AvatarUtil.loadIconIntoImageView(recipient, this);
} else {
setAvatar(GlideApp.with(this), recipient, false);
}
}
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
if (recipient != null) {
RecipientContactPhoto photo = new RecipientContactPhoto(recipient);

Wyświetl plik

@ -1196,7 +1196,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleDisplayGroupRecipients() {
new GroupMembersDialog(this, getRecipient()).display();
new GroupMembersDialog(this, getRecipient(), getLifecycle()).display();
}
private void handleAddToContacts() {

Wyświetl plik

@ -0,0 +1,35 @@
package org.thoughtcrime.securesms.groups.ui;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.recipients.Recipient;
public abstract class GroupMemberEntry {
private @Nullable Runnable onClick;
private GroupMemberEntry() {
}
public void setOnClick(@NonNull Runnable onClick) {
this.onClick = onClick;
}
public @Nullable Runnable getOnClick() {
return onClick;
}
public static class FullMember extends GroupMemberEntry {
private final Recipient member;
public FullMember(@NonNull Recipient member) {
this.member = member;
}
public Recipient getMember() {
return member;
}
}
}

Wyświetl plik

@ -0,0 +1,115 @@
package org.thoughtcrime.securesms.groups.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.ArrayList;
import java.util.Collection;
final class GroupMemberListAdapter extends RecyclerView.Adapter<GroupMemberListAdapter.ViewHolder> {
private static final int FULL_MEMBER = 0;
private final ArrayList<GroupMemberEntry> data = new ArrayList<>();
void updateData(@NonNull Collection<? extends GroupMemberEntry> recipients) {
data.clear();
data.addAll(recipients);
notifyDataSetChanged();
}
@Override
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case FULL_MEMBER:
return new FullMemberViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.group_recipient_list_item,
parent, false));
default:
throw new AssertionError();
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.bind(data.get(position));
}
@Override
public int getItemViewType(int position) {
GroupMemberEntry groupMemberEntry = data.get(position);
if (groupMemberEntry instanceof GroupMemberEntry.FullMember) {
return FULL_MEMBER;
}
throw new AssertionError();
}
@Override
public int getItemCount() {
return data.size();
}
static abstract class ViewHolder extends RecyclerView.ViewHolder {
final Context context;
private final AvatarImageView avatar;
private final TextView recipient;
final PopupMenuView popupMenu;
ViewHolder(@NonNull View itemView) {
super(itemView);
context = itemView.getContext();
avatar = itemView.findViewById(R.id.recipient_avatar);
recipient = itemView.findViewById(R.id.recipient_name);
popupMenu = itemView.findViewById(R.id.popupMenu);
}
void bindRecipient(@NonNull Recipient recipient) {
String displayName = recipient.isLocalNumber() ? context.getString(R.string.GroupMembersDialog_you)
: recipient.getDisplayName(itemView.getContext());
bindImageAndText(recipient, displayName);
}
void bindImageAndText(@NonNull Recipient recipient, @NonNull String displayText) {
this.recipient.setText(displayText);
this.avatar.setRecipient(recipient);
}
void bind(@NonNull GroupMemberEntry memberEntry) {
Runnable onClick = memberEntry.getOnClick();
View.OnClickListener onClickListener = v -> { if (onClick != null) onClick.run(); };
this.avatar.setOnClickListener(onClickListener);
this.recipient.setOnClickListener(onClickListener);
}
}
final static class FullMemberViewHolder extends ViewHolder {
FullMemberViewHolder(@NonNull View itemView) {
super(itemView);
}
@Override
void bind(@NonNull GroupMemberEntry memberEntry) {
super.bind(memberEntry);
GroupMemberEntry.FullMember fullMember = (GroupMemberEntry.FullMember) memberEntry;
bindRecipient(fullMember.getMember());
}
}
}

Wyświetl plik

@ -0,0 +1,62 @@
package org.thoughtcrime.securesms.groups.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import java.util.Collection;
public final class GroupMemberListView extends RecyclerView {
private final GroupMemberListAdapter membersAdapter = new GroupMemberListAdapter();
private int maxHeight;
public GroupMemberListView(@NonNull Context context) {
super(context);
initialize(context, null);
}
public GroupMemberListView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public GroupMemberListView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context, attrs);
}
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
setHasFixedSize(true);
setLayoutManager(new LinearLayoutManager(context));
setAdapter(membersAdapter);
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GroupMemberListView, 0, 0);
try {
maxHeight = typedArray.getDimensionPixelSize(R.styleable.GroupMemberListView_maxHeight, 0);
} finally {
typedArray.recycle();
}
}
}
public void setMembers(@NonNull Collection<? extends GroupMemberEntry> recipients) {
membersAdapter.updateData(recipients);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (maxHeight > 0) {
heightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthSpec, heightSpec);
}
}

Wyświetl plik

@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.groups.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MenuInflater;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.annotation.MenuRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.PopupMenu;
import org.thoughtcrime.securesms.R;
public final class PopupMenuView extends View {
private @MenuRes int menu;
private @Nullable ItemClick callback;
public PopupMenuView(Context context) {
super(context);
init();
}
public PopupMenuView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public PopupMenuView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setBackgroundResource(R.drawable.ic_more_vert_24);
setOnClickListener(v -> {
if (callback != null) {
PopupMenu popup = new PopupMenu(getContext(), v);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(menu, popup.getMenu());
popup.setOnMenuItemClickListener(item -> callback.onItemClick(item.getItemId()));
popup.show();
}
});
}
public void setMenu(@MenuRes int menu, @NonNull ItemClick callback) {
this.menu = menu;
this.callback = callback;
}
public interface ItemClick {
boolean onItemClick(@IdRes int menuItemId);
}
}

Wyświetl plik

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.thoughtcrime.securesms.groups.ui.GroupMemberListView
android:id="@+id/list_members"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:scrollIndicators="top|bottom"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:maxHeight="280dp"
tools:ignore="RtlSymmetry,UnusedAttribute"
tools:listitem="@layout/group_recipient_list_item" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="64dp">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/recipient_avatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/recipient_name"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:textColor="?title_text_color_primary"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/recipient_avatar"
app:layout_constraintEnd_toStartOf="@+id/popupMenu"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/recipient_avatar"
app:layout_constraintTop_toTopOf="@+id/recipient_avatar"
tools:text="@tools:sample/full_names" />
<org.thoughtcrime.securesms.groups.ui.PopupMenuView
android:id="@+id/popupMenu"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -286,11 +286,16 @@
</attr>
</declare-styleable>
<attr name="minWidth" format="dimension" />
<attr name="maxWidth" format="dimension" />
<attr name="minHeight" format="dimension" />
<attr name="maxHeight" format="dimension" />
<declare-styleable name="ThumbnailView">
<attr name="minWidth" format="dimension" />
<attr name="maxWidth" format="dimension" />
<attr name="minHeight" format="dimension" />
<attr name="maxHeight" format="dimension" />
<attr name="minWidth" />
<attr name="maxWidth" />
<attr name="minHeight" />
<attr name="maxHeight" />
<attr name="thumbnail_radius" format="dimension" />
<attr name="thumbnail_fit" format="enum">
<enum name="center_crop" value="0" />
@ -479,4 +484,8 @@
<attr name="thumbTouchRadius" format="dimension" />
</declare-styleable>
<declare-styleable name="GroupMemberListView">
<attr name="maxHeight" />
</declare-styleable>
</resources>