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

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M12,1A11,11 0,1 0,23 12,11 11,0 0,0 12,1ZM12,2.5a9.448,9.448 0,0 1,6.159 2.281L4.781,18.159A9.488,9.488 0,0 1,12 2.5ZM12,21.5a9.448,9.448 0,0 1,-6.159 -2.281L19.219,5.841A9.488,9.488 0,0 1,12 21.5Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M12,2.5A9.5,9.5 0,1 1,2.5 12,9.511 9.511,0 0,1 12,2.5M12,1A11,11 0,1 0,23 12,11 11,0 0,0 12,1ZM12,8.5A1.5,1.5 0,0 0,13.5 7a1.5,1.5 0,1 0,-2.56 1.06A1.435,1.435 0,0 0,12 8.5ZM13,16.5L13,10L9.5,10v1.5h2v5L9,16.5L9,18h6L15,16.5Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M12,1A11,11 0,1 0,23 12,11 11,0 0,0 12,1ZM10.94,5.939A1.5,1.5 0,0 1,13.5 7a1.5,1.5 0,0 1,-2.56 1.06,1.5 1.5,0 0,1 0,-2.121ZM15,18H9V16.5h2.5v-5h-2V10H13v6.5h2Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M8.5,14.25V21H7V14.25ZM18,2H10A3,3 0,0 0,7 5V9.75H8.5V5A1.5,1.5 0,0 1,10 3.5h8A1.5,1.5 0,0 1,19.5 5V21H21V5A3,3 0,0 0,18 2ZM11.906,6.485l-1.06,1.06 2.976,2.977 1.019,0.728H2v1.5H14.841l-1.143,0.817 -2.855,2.89 1.066,1.055 5.479,-5.545Z" />
</vector>

View File

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android"
<bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
android:tint="?icon_tint" android:height="24dp"
android:src="@drawable/ic_message_outline_24" /> android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M12,4c4.963,0 9,3.364 9,7.5S16.963,19 12,19a10.536,10.536 0,0 1,-3.19 -0.5l-0.465,-0.148 -0.464,0.155L4.739,19.556l0.394,-2.365 0.118,-0.707 -0.477,-0.536A6.651,6.651 0,0 1,3 11.5C3,7.364 7.037,4 12,4m0,-1.5c-5.8,0 -10.5,4.029 -10.5,9a8.164,8.164 0,0 0,2.153 5.445l-0.639,3.836c-0.073,0.438 0.144,0.719 0.505,0.719a0.9,0.9 0,0 0,0.279 -0.049l4.558,-1.519A12.019,12.019 0,0 0,12 20.5c5.8,0 10.5,-4.029 10.5,-9s-4.773,-9 -10.5,-9Z" />
</vector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?icon_tint"
android:src="@drawable/ic_message_outline_24" />

View File

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android"
<bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
android:tint="?icon_tint" android:height="24dp"
android:src="@drawable/ic_message_solid_24" /> android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M12,2.5c-5.8,0 -10.5,4.029 -10.5,9a8.164,8.164 0,0 0,2.153 5.445l-0.639,3.836c-0.09,0.542 0.263,0.844 0.784,0.67l4.558,-1.519A12.019,12.019 0,0 0,12 20.5c5.8,0 10.5,-4.029 10.5,-9S17.727,2.5 12,2.5Z" />
</vector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?icon_tint"
android:src="@drawable/ic_message_solid_24" />

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M6.34,1.5a0.76,0.76 0,0 1,0.41 0.13A0.75,0.75 0,0 1,7 2L8.47,6a0.77,0.77 0,0 1,0 0.45,0.79 0.79,0 0,1 -0.27,0.38 10.72,10.72 0,0 1,-1.34 0.83l-1.36,0.7 0.74,1.35a15.13,15.13 0,0 0,6 6l1.35,0.74 0.7,-1.36a11.63,11.63 0,0 1,0.83 -1.35,0.76 0.76,0 0,1 0.83,-0.24L20,15a0.71,0.71 0,0 1,0.36 0.28,0.68 0.68,0 0,1 0.12,0.44A9.39,9.39 0,0 1,20 18.28a3.27,3.27 0,0 1,-3.06 2.22,3.81 3.81,0 0,1 -0.59,-0.05 18.49,18.49 0,0 1,-9.68 -5.09A18.39,18.39 0,0 1,1.56 5.68,3.19 3.19,0 0,1 2,3.44 3.23,3.23 0,0 1,3.72 2,9 9,0 0,1 6.29,1.5ZM6.34,0L6.19,0a10.82,10.82 0,0 0,-3 0.61A4.72,4.72 0,0 0,0.65 2.7,4.78 4.78,0 0,0 0.08,6 20,20 0,0 0,5.59 16.42a19.9,19.9 0,0 0,10.47 5.5,4.91 4.91,0 0,0 0.86,0.08 4.81,4.81 0,0 0,2.74 -0.89,4.75 4.75,0 0,0 1.73,-2.32 10.81,10.81 0,0 0,0.6 -3,2.19 2.19,0 0,0 -1.46,-2.23l-4,-1.46a2.22,2.22 0,0 0,-2.55 0.77,12.73 12.73,0 0,0 -1,1.54A13.74,13.74 0,0 1,7.57 9a11.65,11.65 0,0 0,1.54 -1,2.17 2.17,0 0,0 0.81,-1.14 2.23,2.23 0,0 0,0 -1.4l-1.46,-4A2.35,2.35 0,0 0,7.61 0.4,2.22 2.22,0 0,0 6.34,0Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/icon_tint"
android:pathData="M21.11,16.608a26.24,26.24 0,0 1,-4.513 -2.5,1.384 1.384,0 0,0 -1.918,0.284 9.116,9.116 0,0 0,-0.866 1.465,4.5 4.5,0 0,0 -0.357,1.242A13.113,13.113 0,0 1,6.9 10.542a4.474,4.474 0,0 0,1.243 -0.355,9.019 9.019,0 0,0 1.343,-0.779 1.444,1.444 0,0 0,0.4 -2A26.2,26.2 0,0 1,7.357 2.9a1.42,1.42 0,0 0,-1.71 -0.825,8.63 8.63,0 0,0 -1.1,0.421 4.284,4.284 0,0 0,-2.5 4.392l-0.014,0A16.948,16.948 0,0 0,17.073 21.953l0,-0.016a4.308,4.308 0,0 0,4.441 -2.492,8.732 8.732,0 0,0 0.431,-1.13A1.42,1.42 0,0 0,21.11 16.608Z" />
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="8dp"
android:topRightRadius="8dp" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/core_grey_75" />
<corners
android:topLeftRadius="8dp"
android:topRightRadius="8dp" />
</shape>

View File

@ -0,0 +1,138 @@
<?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="match_parent"
tools:theme="@style/Signal.RecipientBottomSheet.Light">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/recipient_avatar"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/full_name"
style="@style/TextAppearance.Signal.Body1.Bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="?attr/title_text_color_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recipient_avatar"
tools:text="Miyuki Shibata" />
<TextView
android:id="@+id/username_number"
style="@style/TextAppearance.Signal.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="?attr/title_text_color_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/full_name"
tools:text="\@shibatabread +1 555-654-6657" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username_number">
<Button
android:id="@+id/message_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/RecipientBottomSheet_message"
app:drawableStartCompat="?attr/recipient_message_icon" />
<Button
android:id="@+id/secure_call_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_call"
app:drawableStartCompat="?attr/recipient_call_icon" />
<Button
android:id="@+id/block_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_block"
android:visibility="gone"
app:drawableStartCompat="?attr/recipient_block_icon"
tools:visibility="visible" />
<Button
android:id="@+id/unblock_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_unblock"
android:visibility="gone"
app:drawableStartCompat="?attr/recipient_block_icon"
tools:visibility="visible" />
<Button
android:id="@+id/view_safety_number_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_view_safety_number"
app:drawableStartCompat="?attr/recipient_view_safety_icon" />
<Button
android:id="@+id/make_group_admin_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_make_group_admin"
android:visibility="gone"
app:drawableStartCompat="?attr/recipient_make_admin_icon"
tools:visibility="visible" />
<Button
android:id="@+id/remove_group_admin_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_remove_as_admin"
android:visibility="gone"
app:drawableStartCompat="?attr/recipient_make_admin_icon"
tools:visibility="visible" />
<Button
android:id="@+id/remove_from_group_button"
style="@style/Signal.Button.TextButton.Drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/RecipientBottomSheet_remove_from_group"
android:visibility="gone"
app:drawableStartCompat="?attr/recipient_remove_icon"
tools:visibility="visible" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -281,6 +281,13 @@
<attr name="debuglog_color_warn" format="color" /> <attr name="debuglog_color_warn" format="color" />
<attr name="debuglog_color_error" format="color" /> <attr name="debuglog_color_error" format="color" />
<attr name="recipient_message_icon" format="reference"/>
<attr name="recipient_call_icon" format="reference"/>
<attr name="recipient_block_icon" format="reference"/>
<attr name="recipient_view_safety_icon" format="reference"/>
<attr name="recipient_make_admin_icon" format="reference"/>
<attr name="recipient_remove_icon" format="reference"/>
<declare-styleable name="ColorPreference"> <declare-styleable name="ColorPreference">
<attr name="itemLayout" format="reference" /> <attr name="itemLayout" format="reference" />
<attr name="choices" format="reference" /> <attr name="choices" format="reference" />

View File

@ -2131,6 +2131,16 @@
<string name="RegistrationLockDialog_reminder">Reminder:</string> <string name="RegistrationLockDialog_reminder">Reminder:</string>
<string name="recipient_preferences__about">About</string> <string name="recipient_preferences__about">About</string>
<string name="Recipient_unknown">Unknown</string> <string name="Recipient_unknown">Unknown</string>
<!-- RecipientBottomSheet -->
<string name="RecipientBottomSheet_message">Message</string>
<string name="RecipientBottomSheet_call">Call</string>
<string name="RecipientBottomSheet_block">Block</string>
<string name="RecipientBottomSheet_unblock">Unblock</string>
<string name="RecipientBottomSheet_view_safety_number">View safety number</string>
<string name="RecipientBottomSheet_make_group_admin">Make group admin</string>
<string name="RecipientBottomSheet_remove_as_admin">Remove as admin</string>
<string name="RecipientBottomSheet_remove_from_group">Remove from group</string>
<!-- EOF --> <!-- EOF -->
</resources> </resources>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" <resources xmlns:tools="http://schemas.android.com/tools">
xmlns:app="http://schemas.android.com/apk/res-auto">
<style name="NoAnimation.Theme.BlackScreen" parent="Theme.AppCompat.NoActionBar"> <style name="NoAnimation.Theme.BlackScreen" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowAnimationStyle">@null</item> <item name="android:windowAnimationStyle">@null</item>
<item name="android:windowBackground">@android:color/black</item> <item name="android:windowBackground">@android:color/black</item>
@ -408,4 +407,13 @@
<enum name="textureView" value="1"/> <enum name="textureView" value="1"/>
</attr> </attr>
</declare-styleable> </declare-styleable>
<style name="Signal.Button.TextButton" parent="Widget.AppCompat.Button.Borderless">
<item name="android:textAppearance">@style/TextAppearance.Signal.Body1</item>
</style>
<style name="Signal.Button.TextButton.Drawable">
<item name="android:textAlignment">viewStart</item>
<item name="android:drawablePadding">16dp</item>
</style>
</resources> </resources>

View File

@ -97,10 +97,18 @@
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>
<style name="TextAppearance.Signal.Body2" parent="@style/TextAppearance.MaterialComponents.Body2"> <style name="TextAppearance.Signal.Body1" parent="@style/TextAppearance.AppCompat.Body1">
<item name="android:textSize">16sp</item>
</style> </style>
<style name="TextAppearance.Signal.Caption" parent="@style/TextAppearance.MaterialComponents.Caption"> <style name="TextAppearance.Signal.Body2" parent="@style/TextAppearance.AppCompat.Body2">
</style>
<style name="TextAppearance.Signal.Body1.Bold">
<item name="android:textStyle">bold</item>
</style>
<style name="TextAppearance.Signal.Caption" parent="@style/TextAppearance.AppCompat.Caption">
</style> </style>
<style name="TextAppearance.Signal.Subtitle2" parent="@style/TextAppearance.MaterialComponents.Subtitle2"> <style name="TextAppearance.Signal.Subtitle2" parent="@style/TextAppearance.MaterialComponents.Subtitle2">

View File

@ -359,7 +359,7 @@
<item name="menu_multi_select_icon">@drawable/ic_select_24</item> <item name="menu_multi_select_icon">@drawable/ic_select_24</item>
<item name="menu_archive_icon">@drawable/ic_archive_white_24dp</item> <item name="menu_archive_icon">@drawable/ic_archive_white_24dp</item>
<item name="message_icon">@drawable/ic_message_outline_tinted_24</item> <item name="message_icon">@drawable/ic_message_outline_tinted_bitmap_24</item>
<item name="notifications_icon">@drawable/ic_bell_outline_24</item> <item name="notifications_icon">@drawable/ic_bell_outline_24</item>
<item name="privacy_icon">@drawable/ic_lock_outline_24</item> <item name="privacy_icon">@drawable/ic_lock_outline_24</item>
<item name="appearance_icon">@drawable/ic_appearance_outline_24</item> <item name="appearance_icon">@drawable/ic_appearance_outline_24</item>
@ -637,7 +637,7 @@
<item name="menu_multi_select_icon">@drawable/ic_select_24</item> <item name="menu_multi_select_icon">@drawable/ic_select_24</item>
<item name="menu_archive_icon">@drawable/ic_archive_white_24dp</item> <item name="menu_archive_icon">@drawable/ic_archive_white_24dp</item>
<item name="message_icon">@drawable/ic_message_solid_tinted_24</item> <item name="message_icon">@drawable/ic_message_solid_tinted_bitmap_24</item>
<item name="notifications_icon">@drawable/ic_bell_solid_24</item> <item name="notifications_icon">@drawable/ic_bell_solid_24</item>
<item name="privacy_icon">@drawable/ic_lock_solid_24</item> <item name="privacy_icon">@drawable/ic_lock_solid_24</item>
<item name="appearance_icon">@drawable/ic_appearance_solid_24</item> <item name="appearance_icon">@drawable/ic_appearance_solid_24</item>
@ -781,4 +781,51 @@
<item name="android:windowIsFloating">false</item> <item name="android:windowIsFloating">false</item>
</style> </style>
<style name="Theme.Design.Light.BottomSheetDialog.Fixed.Rounded">
<item name="bottomSheetStyle">@style/Widget.Design.BottomSheet.Modal.Rounded.Light</item>
</style>
<style name="Theme.Design.BottomSheetDialog.Fixed.Rounded">
<item name="bottomSheetStyle">@style/Widget.Design.BottomSheet.Modal.Rounded</item>
</style>
<style name="Widget.Design.BottomSheet.Modal.Rounded.Light">
<item name="android:background">@drawable/rounded_dialog</item>
</style>
<style name="Widget.Design.BottomSheet.Modal.Rounded">
<item name="android:background">@drawable/rounded_dialog_dark</item>
</style>
<style name="Signal.RecipientBottomSheet.Light" parent="Theme.Design.Light.BottomSheetDialog.Fixed.Rounded">
<item name="icon_tint">@color/core_grey_75</item>
<item name="title_text_color_primary">@color/core_grey_90</item>
<item name="title_text_color_secondary">@color/core_grey_60</item>
<item name="recipient_message_icon">@drawable/ic_message_outline_tinted_24</item>
<item name="recipient_call_icon">@drawable/ic_phone_right_outline_tinted_24</item>
<item name="recipient_block_icon">@drawable/ic_block_tinted_24</item>
<item name="recipient_view_safety_icon">@drawable/ic_info_outline_tinted_24</item>
<item name="recipient_make_admin_icon">@drawable/ic_group_outline_24</item>
<item name="recipient_remove_icon">@drawable/ic_leave_tinted_24</item>
</style>
<style name="Signal.RecipientBottomSheet" parent="Theme.Design.BottomSheetDialog.Fixed.Rounded">
<item name="icon_tint">@color/core_grey_15</item>
<item name="title_text_color_primary">@color/core_grey_05</item>
<item name="title_text_color_secondary">@color/core_grey_25</item>
<item name="android:navigationBarColor" tools:ignore="NewApi">@color/core_grey_75</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
<item name="recipient_message_icon">@drawable/ic_message_solid_tinted_24</item>
<item name="recipient_call_icon">@drawable/ic_phone_right_solid_tinted_24</item>
<item name="recipient_block_icon">@drawable/ic_block_tinted_24</item>
<item name="recipient_view_safety_icon">@drawable/ic_info_solid_tinted_24</item>
<item name="recipient_make_admin_icon">@drawable/ic_group_solid_24</item>
<item name="recipient_remove_icon">@drawable/ic_leave_tinted_24</item>
</style>
</resources> </resources>