Recipient bottom sheet.

This commit is contained in:
Alan Evans
2020-04-22 16:25:28 -03:00
committed by Greyson Parrelli
parent f6f6496c9c
commit 17c5b858b5
22 changed files with 747 additions and 29 deletions

View File

@@ -1,43 +1,45 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Lifecycle;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.groups.GroupId;
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.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import java.util.ArrayList;
public final class GroupMembersDialog {
private final Context context;
private final Recipient groupRecipient;
private final Lifecycle lifecycle;
private final FragmentActivity fragmentActivity;
private final Recipient groupRecipient;
private final Lifecycle lifecycle;
public GroupMembersDialog(@NonNull Context context,
public GroupMembersDialog(@NonNull FragmentActivity activity,
@NonNull Recipient groupRecipient,
@NonNull Lifecycle lifecycle)
{
this.context = context;
this.groupRecipient = groupRecipient;
this.lifecycle = lifecycle;
this.fragmentActivity = activity;
this.groupRecipient = groupRecipient;
this.lifecycle = lifecycle;
}
public void display() {
SimpleTask.run(
lifecycle,
() -> DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupRecipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_INCLUDING_SELF),
() -> DatabaseFactory.getGroupDatabase(fragmentActivity).getGroupMembers(groupRecipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_INCLUDING_SELF),
members -> {
AlertDialog dialog = new AlertDialog.Builder(context)
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
.setTitle(R.string.ConversationActivity_group_members)
.setIconAttribute(R.attr.group_members_dialog_icon)
.setCancelable(true)
@@ -70,13 +72,18 @@ public final class GroupMembersDialog {
}
private void contactClick(@NonNull Recipient recipient) {
if (recipient.getContactUri() != null) {
Intent intent = new Intent(context, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
GroupId groupId = groupRecipient.requireGroupId();
context.startActivity(intent);
if (groupId.isV2()) {
RecipientBottomSheetDialogFragment.create(recipient.getId(), groupId)
.show(fragmentActivity.getSupportFragmentManager(), "BOTTOM");
} else if (recipient.getContactUri() != null) {
Intent intent = new Intent(fragmentActivity, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
fragmentActivity.startActivity(intent);
} else {
context.startActivity(RecipientExporter.export(recipient).asAddContactIntent());
fragmentActivity.startActivity(RecipientExporter.export(recipient).asAddContactIntent());
}
}
}

View File

@@ -0,0 +1,158 @@
package org.thoughtcrime.securesms.recipients.ui.bottomsheet;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ThemeUtil;
import java.util.Objects;
public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogFragment {
private static final String ARGS_RECIPIENT_ID = "RECIPIENT_ID";
private static final String ARGS_GROUP_ID = "GROUP_ID";
private RecipientDialogViewModel viewModel;
private AvatarImageView avatar;
private TextView fullName;
private TextView usernameNumber;
private Button messageButton;
private Button secureCallButton;
private Button blockButton;
private Button unblockButton;
private Button viewSafetyNumberButton;
private Button makeGroupAdminButton;
private Button removeAdminButton;
private Button removeFromGroupButton;
public static BottomSheetDialogFragment create(@NonNull RecipientId recipientId,
@Nullable GroupId groupId)
{
Bundle args = new Bundle();
RecipientBottomSheetDialogFragment fragment = new RecipientBottomSheetDialogFragment();
args.putString(ARGS_RECIPIENT_ID, recipientId.serialize());
if (groupId != null) {
args.putString(ARGS_GROUP_ID, groupId.toString());
}
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
setStyle(DialogFragment.STYLE_NORMAL,
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Signal_RecipientBottomSheet
: R.style.Signal_RecipientBottomSheet_Light);
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipient_bottom_sheet, container, false);
avatar = view.findViewById(R.id.recipient_avatar);
fullName = view.findViewById(R.id.full_name);
usernameNumber = view.findViewById(R.id.username_number);
messageButton = view.findViewById(R.id.message_button);
secureCallButton = view.findViewById(R.id.secure_call_button);
blockButton = view.findViewById(R.id.block_button);
unblockButton = view.findViewById(R.id.unblock_button);
viewSafetyNumberButton = view.findViewById(R.id.view_safety_number_button);
makeGroupAdminButton = view.findViewById(R.id.make_group_admin_button);
removeAdminButton = view.findViewById(R.id.remove_group_admin_button);
removeFromGroupButton = view.findViewById(R.id.remove_from_group_button);
return view;
}
@Override
public void onViewCreated(@NonNull View fragmentView, @Nullable Bundle savedInstanceState) {
super.onViewCreated(fragmentView, savedInstanceState);
Bundle arguments = requireArguments();
RecipientId recipientId = RecipientId.from(Objects.requireNonNull(arguments.getString(ARGS_RECIPIENT_ID)));
GroupId groupId = GroupId.parseNullableOrThrow(arguments.getString(ARGS_GROUP_ID));
RecipientDialogViewModel.Factory factory = new RecipientDialogViewModel.Factory(requireContext().getApplicationContext(), recipientId, groupId);
viewModel = ViewModelProviders.of(this, factory).get(RecipientDialogViewModel.class);
viewModel.getRecipient().observe(getViewLifecycleOwner(), recipient -> {
avatar.setRecipient(recipient);
String name = recipient.getProfileName().toString();
fullName.setText(name);
fullName.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE);
String usernameNumberString = String.format("%s %s", recipient.getUsername().or(""), recipient.getSmsAddress().or(""))
.trim();
usernameNumber.setText(usernameNumberString);
usernameNumber.setVisibility(TextUtils.isEmpty(usernameNumberString) ? View.GONE : View.VISIBLE);
boolean blocked = recipient.isBlocked();
blockButton.setVisibility(blocked ? View.GONE : View.VISIBLE);
unblockButton.setVisibility(blocked ? View.VISIBLE : View.GONE);
secureCallButton.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);
});
viewModel.getAdminActionStatus().observe(getViewLifecycleOwner(), adminStatus -> {
makeGroupAdminButton.setVisibility(adminStatus.isCanMakeAdmin() ? View.VISIBLE : View.GONE);
removeAdminButton.setVisibility(adminStatus.isCanMakeNonAdmin() ? View.VISIBLE : View.GONE);
removeFromGroupButton.setVisibility(adminStatus.isCanRemove() ? View.VISIBLE : View.GONE);
});
viewModel.getIdentity().observe(getViewLifecycleOwner(), identityRecord -> {
viewSafetyNumberButton.setVisibility(identityRecord != null ? View.VISIBLE : View.GONE);
if (identityRecord != null) {
viewSafetyNumberButton.setOnClickListener(view -> {
dismiss();
viewModel.onViewSafetyNumberClicked(requireActivity(), identityRecord);
});
}
});
avatar.setOnClickListener(view -> {
dismiss();
viewModel.onAvatarClicked(requireActivity());
});
messageButton.setOnClickListener(view -> {
dismiss();
viewModel.onMessageClicked(requireActivity());
});
secureCallButton.setOnClickListener(view -> {
dismiss();
viewModel.onSecureCallClicked(requireActivity());
});
blockButton.setOnClickListener(view -> viewModel.onBlockClicked(requireActivity()));
unblockButton.setOnClickListener(view -> viewModel.onUnblockClicked(requireActivity()));
makeGroupAdminButton.setOnClickListener(view -> viewModel.onMakeGroupAdminClicked());
removeAdminButton.setOnClickListener(view -> viewModel.onRemoveGroupAdminClicked());
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked());
}
}

View File

@@ -0,0 +1,82 @@
package org.thoughtcrime.securesms.recipients.ui.bottomsheet;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
final class RecipientDialogRepository {
@NonNull private final GroupDatabase groupDatabase;
@NonNull private final Context context;
@NonNull private final RecipientId recipientId;
@Nullable private final GroupId groupId;
RecipientDialogRepository(@NonNull Context context,
@NonNull RecipientId recipientId,
@Nullable GroupId groupId)
{
this.context = context;
this.groupDatabase = DatabaseFactory.getGroupDatabase(context);
this.recipientId = recipientId;
this.groupId = groupId;
}
@NonNull RecipientId getRecipientId() {
return recipientId;
}
@Nullable GroupId getGroupId() {
return groupId;
}
void isAdminOfGroup(@NonNull RecipientId recipientId, @NonNull AdminCallback callback) {
SimpleTask.run(SignalExecutors.BOUNDED,
() -> {
if (groupId != null) {
Recipient recipient = Recipient.resolved(recipientId);
return groupDatabase.getGroup(groupId)
.transform(g -> g.isAdmin(recipient))
.or(false);
} else {
return false;
}
},
callback::isAdmin);
}
void getIdentity(@NonNull IdentityCallback callback) {
SimpleTask.run(SignalExecutors.BOUNDED,
() -> DatabaseFactory.getIdentityDatabase(context)
.getIdentity(recipientId)
.orNull(),
callback::remoteIdentity);
}
public void getRecipient(@NonNull RecipientCallback recipientCallback) {
SimpleTask.run(SignalExecutors.BOUNDED,
() -> Recipient.resolved(recipientId),
recipientCallback::onRecipient);
}
interface AdminCallback {
void isAdmin(boolean admin);
}
interface IdentityCallback {
void remoteIdentity(@Nullable IdentityDatabase.IdentityRecord identityRecord);
}
interface RecipientCallback {
void onRecipient(@NonNull Recipient recipient);
}
}

View File

@@ -0,0 +1,158 @@
package org.thoughtcrime.securesms.recipients.ui.bottomsheet;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.RecipientPreferenceActivity;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.livedata.LiveDataPair;
final class RecipientDialogViewModel extends ViewModel {
private final Context context;
private final RecipientDialogRepository recipientDialogRepository;
private final LiveData<Recipient> recipient;
private final MutableLiveData<IdentityDatabase.IdentityRecord> identity;
private final LiveData<AdminActionStatus> adminActionStatus;
private RecipientDialogViewModel(@NonNull Context context,
@NonNull RecipientDialogRepository recipientDialogRepository)
{
this.context = context;
this.recipientDialogRepository = recipientDialogRepository;
this.identity = new MutableLiveData<>();
MutableLiveData<Boolean> localIsAdmin = new DefaultValueLiveData<>(false);
MutableLiveData<Boolean> recipientIsAdmin = new DefaultValueLiveData<>(false);
if (recipientDialogRepository.getGroupId() != null && recipientDialogRepository.getGroupId().isV2()) {
recipientDialogRepository.isAdminOfGroup(Recipient.self().getId(), localIsAdmin::setValue);
recipientDialogRepository.isAdminOfGroup(recipientDialogRepository.getRecipientId(), recipientIsAdmin::setValue);
}
adminActionStatus = Transformations.map(new LiveDataPair<>(localIsAdmin, recipientIsAdmin, false, false),
pair -> {
boolean localAdmin = pair.first();
boolean recipientAdmin = pair.second();
return new AdminActionStatus(localAdmin,
localAdmin && !recipientAdmin,
localAdmin && recipientAdmin);
});
recipient = Recipient.live(recipientDialogRepository.getRecipientId()).getLiveData();
recipientDialogRepository.getIdentity(identity::setValue);
}
LiveData<Recipient> getRecipient() {
return recipient;
}
LiveData<AdminActionStatus> getAdminActionStatus() {
return adminActionStatus;
}
LiveData<IdentityDatabase.IdentityRecord> getIdentity() {
return identity;
}
void onMessageClicked(@NonNull Activity activity) {
recipientDialogRepository.getRecipient(recipient -> CommunicationActions.startConversation(activity, recipient, null));
}
void onSecureCallClicked(@NonNull Activity activity) {
recipientDialogRepository.getRecipient(recipient -> CommunicationActions.startVoiceCall(activity, recipient));
}
void onBlockClicked(@NonNull FragmentActivity activity) {
recipientDialogRepository.getRecipient(recipient -> BlockUnblockDialog.showBlockFor(activity, activity.getLifecycle(), recipient, () -> RecipientUtil.block(context, recipient)));
}
void onUnblockClicked(@NonNull FragmentActivity activity) {
recipientDialogRepository.getRecipient(recipient -> BlockUnblockDialog.showUnblockFor(activity, activity.getLifecycle(), recipient, () -> RecipientUtil.unblock(context, recipient)));
}
void onViewSafetyNumberClicked(@NonNull Activity activity, @NonNull IdentityDatabase.IdentityRecord identityRecord) {
activity.startActivity(VerifyIdentityActivity.newIntent(activity, identityRecord));
}
void onAvatarClicked(@NonNull Activity activity) {
activity.startActivity(RecipientPreferenceActivity.getLaunchIntent(activity, recipientDialogRepository.getRecipientId()));
}
void onMakeGroupAdminClicked() {
// TODO GV2
throw new AssertionError("NYI");
}
void onRemoveGroupAdminClicked() {
// TODO GV2
throw new AssertionError("NYI");
}
void onRemoveFromGroupClicked() {
// TODO GV2
throw new AssertionError("NYI");
}
static class AdminActionStatus {
private final boolean canRemove;
private final boolean canMakeAdmin;
private final boolean canMakeNonAdmin;
AdminActionStatus(boolean canRemove, boolean canMakeAdmin, boolean canMakeNonAdmin) {
this.canRemove = canRemove;
this.canMakeAdmin = canMakeAdmin;
this.canMakeNonAdmin = canMakeNonAdmin;
}
boolean isCanRemove() {
return canRemove;
}
boolean isCanMakeAdmin() {
return canMakeAdmin;
}
boolean isCanMakeNonAdmin() {
return canMakeNonAdmin;
}
}
public static class Factory implements ViewModelProvider.Factory {
private final Context context;
private final RecipientId recipientId;
private final GroupId groupId;
Factory(@NonNull Context context, @NonNull RecipientId recipientId, @Nullable GroupId groupId) {
this.context = context;
this.recipientId = recipientId;
this.groupId = groupId;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new RecipientDialogViewModel(context, new RecipientDialogRepository(context, recipientId, groupId));
}
}
}