Refactor conversation settings screens into a single fragment with new UI.

This commit is contained in:
Alex Hart
2021-06-24 13:52:54 -03:00
committed by Cody Henthorne
parent f19033a7a2
commit da2ee33dff
121 changed files with 4394 additions and 4076 deletions

View File

@@ -1,130 +0,0 @@
package org.thoughtcrime.securesms.recipients.ui;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.appbar.AppBarLayout;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.ref.WeakReference;
public final class RecipientSettingsCoordinatorLayoutBehavior extends CoordinatorLayout.Behavior<View> {
private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
private final ViewReference avatarTargetRef = new ViewReference(R.id.avatar_target);
private final ViewReference nameRef = new ViewReference(R.id.name);
private final ViewReference nameTargetRef = new ViewReference(R.id.name_target);
private final Rect targetRect = new Rect();
private final Rect childRect = new Rect();
public RecipientSettingsCoordinatorLayoutBehavior(@NonNull Context context, @Nullable AttributeSet attrs) {
}
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
int range = appBarLayout.getTotalScrollRange();
float factor = INTERPOLATOR.getInterpolation(-appBarLayout.getY() / range);
updateAvatarPositionAndScale(parent, child, factor);
updateNamePosition(parent, factor);
return true;
}
private void updateAvatarPositionAndScale(@NonNull CoordinatorLayout parent, @NonNull View child, float factor) {
View target = avatarTargetRef.require(parent);
targetRect.set(target.getLeft(), target.getTop(), target.getRight(), target.getBottom());
childRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
float widthScale = 1f - (1f - (targetRect.width() / (float) childRect.width())) * factor;
float heightScale = 1f - (1f - (targetRect.height() / (float) childRect.height())) * factor;
float superimposedLeft = childRect.left + (childRect.width() - targetRect.width()) / 2f;
float superimposedTop = childRect.top + (childRect.height() - targetRect.height()) / 2f;
float xTranslation = (targetRect.left - superimposedLeft) * factor;
float yTranslation = (targetRect.top - superimposedTop) * factor;
child.setScaleX(widthScale);
child.setScaleY(heightScale);
child.setTranslationX(xTranslation);
child.setTranslationY(yTranslation);
}
private void updateNamePosition(@NonNull CoordinatorLayout parent, float factor) {
TextView child = (TextView) nameRef.require(parent);
View target = nameTargetRef.require(parent);
targetRect.set(target.getLeft(), target.getTop(), target.getRight(), target.getBottom());
childRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
if (child.getMaxWidth() != targetRect.width()) {
child.setMaxWidth(targetRect.width());
}
float deltaTop = targetRect.top - childRect.top;
float deltaStart = getStart(parent, targetRect) - getStart(parent, childRect);
float yTranslation = deltaTop * factor;
float xTranslation = deltaStart * factor;
child.setTranslationY(yTranslation);
child.setTranslationX(xTranslation);
}
private static int getStart(@NonNull CoordinatorLayout parent, @NonNull Rect rect) {
return ViewUtil.isLtr(parent) ? rect.left : rect.right;
}
private static final class ViewReference {
private WeakReference<View> ref = new WeakReference<>(null);
private final @IdRes int idRes;
private ViewReference(@IdRes int idRes) {
this.idRes = idRes;
}
private @NonNull View require(@NonNull View parent) {
View view = ref.get();
if (view == null) {
view = getChildOrThrow(parent, idRes);
ref = new WeakReference<>(view);
}
return view;
}
private static @NonNull View getChildOrThrow(@NonNull View parent, @IdRes int id) {
View child = parent.findViewById(id);
if (child == null) {
throw new AssertionError("Can't find view with ID " + R.id.avatar_target);
} else {
return child;
}
}
}
}

View File

@@ -25,6 +25,8 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon;
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
import org.thoughtcrime.securesms.groups.GroupId;
@@ -34,6 +36,7 @@ import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
@@ -41,6 +44,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
import kotlin.Unit;
/**
* A bottom sheet that shows some simple recipient details, as well as some actions (like calling,
* adding to contacts, etc).
@@ -57,10 +62,6 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
private TextView fullName;
private TextView about;
private TextView usernameNumber;
private Button messageButton;
private Button secureCallButton;
private Button insecureCallButton;
private Button secureVideoCallButton;
private Button blockButton;
private Button unblockButton;
private Button addContactButton;
@@ -71,6 +72,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
private Button removeFromGroupButton;
private ProgressBar adminActionBusy;
private View noteToSelfDescription;
private View buttonStrip;
public static BottomSheetDialogFragment create(@NonNull RecipientId recipientId,
@Nullable GroupId groupId)
@@ -105,10 +107,6 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
fullName = view.findViewById(R.id.rbs_full_name);
about = view.findViewById(R.id.rbs_about);
usernameNumber = view.findViewById(R.id.rbs_username_number);
messageButton = view.findViewById(R.id.rbs_message_button);
secureCallButton = view.findViewById(R.id.rbs_secure_call_button);
insecureCallButton = view.findViewById(R.id.rbs_insecure_call_button);
secureVideoCallButton = view.findViewById(R.id.rbs_video_call_button);
blockButton = view.findViewById(R.id.rbs_block_button);
unblockButton = view.findViewById(R.id.rbs_unblock_button);
addContactButton = view.findViewById(R.id.rbs_add_contact_button);
@@ -119,6 +117,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
removeFromGroupButton = view.findViewById(R.id.rbs_remove_from_group_button);
adminActionBusy = view.findViewById(R.id.rbs_admin_action_busy);
noteToSelfDescription = view.findViewById(R.id.rbs_note_to_self_description);
buttonStrip = view.findViewById(R.id.button_strip);
return view;
}
@@ -192,10 +191,41 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
unblockButton.setVisibility(View.GONE);
}
messageButton.setVisibility(!recipient.isSelf() ? View.VISIBLE : View.GONE);
secureCallButton.setVisibility(recipient.isRegistered() && !recipient.isSelf() ? View.VISIBLE : View.GONE);
insecureCallButton.setVisibility(!recipient.isRegistered() && !recipient.isSelf() ? View.VISIBLE : View.GONE);
secureVideoCallButton.setVisibility(recipient.isRegistered() && !recipient.isSelf() ? View.VISIBLE : View.GONE);
ButtonStripPreference.State buttonStripState = new ButtonStripPreference.State(
/* isMessageAvailable = */ !recipient.isSelf(),
/* isVideoAvailable = */ recipient.isRegistered() && !recipient.isSelf(),
/* isAudioAvailable = */ !recipient.isSelf(),
/* isMuteAvailable = */ false,
/* isSearchAvailable = */ false,
/* isAudioSecure = */ recipient.isRegistered(),
/* isMuted = */ false
);
ButtonStripPreference.Model buttonStripModel = new ButtonStripPreference.Model(
buttonStripState,
DSLSettingsIcon.from(ContextUtil.requireDrawable(requireContext(), R.drawable.selectable_recipient_bottom_sheet_icon_button)),
() -> {
dismiss();
viewModel.onMessageClicked(requireActivity());
return Unit.INSTANCE;
},
() -> {
viewModel.onSecureVideoCallClicked(requireActivity());
return Unit.INSTANCE;
},
() -> {
if (buttonStripState.isAudioSecure()) {
viewModel.onSecureCallClicked(requireActivity());
} else {
viewModel.onInsecureCallClicked(requireActivity());
}
return Unit.INSTANCE;
},
() -> Unit.INSTANCE,
() -> Unit.INSTANCE
);
new ButtonStripPreference.ViewHolder(buttonStrip).bind(buttonStripModel);
if (recipient.isSystemContact() || recipient.isGroup() || recipient.isSelf()) {
addContactButton.setVisibility(View.GONE);
@@ -234,15 +264,6 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
viewModel.onAvatarClicked(requireActivity());
});
messageButton.setOnClickListener(view -> {
dismiss();
viewModel.onMessageClicked(requireActivity());
});
secureCallButton.setOnClickListener(view -> viewModel.onSecureCallClicked(requireActivity()));
insecureCallButton.setOnClickListener(view -> viewModel.onInsecureCallClicked(requireActivity()));
secureVideoCallButton.setOnClickListener(view -> viewModel.onSecureVideoCallClicked(requireActivity()));
blockButton.setOnClickListener(view -> viewModel.onBlockClicked(requireActivity()));
unblockButton.setOnClickListener(view -> viewModel.onUnblockClicked(requireActivity()));

View File

@@ -19,6 +19,7 @@ import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.groups.GroupId;
@@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.recipients.ui.managerecipient.ManageRecipientActivity;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
@@ -138,13 +138,13 @@ final class RecipientDialogViewModel extends ViewModel {
}
void onAvatarClicked(@NonNull Activity activity) {
activity.startActivity(ManageRecipientActivity.newIntent(activity, recipientDialogRepository.getRecipientId()));
activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId()));
}
void onMakeGroupAdminClicked(@NonNull Activity activity) {
new AlertDialog.Builder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_s_will_be_able_to_edit_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_make_group_admin,
.setPositiveButton(R.string.RecipientBottomSheet_make_admin,
(dialog, which) -> {
adminActionBusy.setValue(true);
recipientDialogRepository.setMemberAdmin(true, result -> {

View File

@@ -1,72 +0,0 @@
package org.thoughtcrime.securesms.recipients.ui.managerecipient;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityOptionsCompat;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class ManageRecipientActivity extends PassphraseRequiredActivity {
private static final String RECIPIENT_ID = "RECIPIENT_ID";
private static final String FROM_CONVERSATION = "FROM_CONVERSATION";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
public static Intent newIntent(@NonNull Context context, @NonNull RecipientId recipientId) {
Intent intent = new Intent(context, ManageRecipientActivity.class);
intent.putExtra(RECIPIENT_ID, recipientId);
return intent;
}
/**
* Makes the message button behave like back.
*/
public static Intent newIntentFromConversation(@NonNull Context context, @NonNull RecipientId recipientId) {
Intent intent = new Intent(context, ManageRecipientActivity.class);
intent.putExtra(RECIPIENT_ID, recipientId);
intent.putExtra(FROM_CONVERSATION, true);
return intent;
}
public static @Nullable Bundle createTransitionBundle(@NonNull Context activityContext, @NonNull View from) {
if (activityContext instanceof Activity) {
return ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) activityContext, from, "avatar").toBundle();
} else {
return null;
}
}
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
}
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready);
getWindow().getDecorView().setSystemUiVisibility(getWindow().getDecorView().getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
setContentView(R.layout.recipient_manage_activity);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, ManageRecipientFragment.newInstance(getIntent().getParcelableExtra(RECIPIENT_ID), getIntent().getBooleanExtra(FROM_CONVERSATION, false)))
.commitNow();
}
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
}

View File

@@ -1,416 +0,0 @@
package org.thoughtcrime.securesms.recipients.ui.managerecipient;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
import androidx.core.widget.TextViewCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProviders;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.AvatarPreviewActivity;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.MuteDialog;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity;
import org.thoughtcrime.securesms.recipients.ui.notifications.CustomNotificationsDialogFragment;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
import java.util.Locale;
import java.util.Objects;
public class ManageRecipientFragment extends LoggingFragment {
private static final String RECIPIENT_ID = "RECIPIENT_ID";
private static final String FROM_CONVERSATION = "FROM_CONVERSATION";
private static final int REQUEST_CODE_RETURN_FROM_MEDIA = 405;
private static final int REQUEST_CODE_ADD_CONTACT = 588;
private static final int REQUEST_CODE_VIEW_CONTACT = 610;
private ManageRecipientViewModel viewModel;
private GroupMemberListView sharedGroupList;
private Toolbar toolbar;
private TextView title;
private TextView about;
private TextView subtitle;
private ViewGroup internalDetails;
private TextView internalDetailsText;
private View disableProfileSharingButton;
private View contactRow;
private TextView contactText;
private ImageView contactIcon;
private AvatarImageView avatar;
private ThreadPhotoRailView threadPhotoRailView;
private View mediaCard;
private ManageRecipientViewModel.CursorFactory cursorFactory;
private View sharedMediaRow;
private View disappearingMessagesCard;
private View disappearingMessagesRow;
private TextView disappearingMessages;
private View blockUnblockCard;
private TextView block;
private TextView unblock;
private View groupMembershipCard;
private TextView addToAGroup;
private SwitchCompat muteNotificationsSwitch;
private View muteNotificationsRow;
private TextView muteNotificationsUntilLabel;
private View notificationsCard;
private TextView customNotificationsButton;
private View customNotificationsRow;
private View toggleAllGroups;
private View viewSafetyNumber;
private TextView groupsInCommonCount;
private View messageButton;
private View secureCallButton;
private View insecureCallButton;
private View secureVideoCallButton;
private TextView chatWallpaperButton;
static ManageRecipientFragment newInstance(@NonNull RecipientId recipientId, boolean fromConversation) {
ManageRecipientFragment fragment = new ManageRecipientFragment();
Bundle args = new Bundle();
args.putParcelable(RECIPIENT_ID, recipientId);
args.putBoolean(FROM_CONVERSATION, fromConversation);
fragment.setArguments(args);
return fragment;
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.recipient_manage_fragment, container, false);
avatar = view.findViewById(R.id.recipient_avatar);
toolbar = view.findViewById(R.id.toolbar);
contactRow = view.findViewById(R.id.recipient_contact_row);
contactText = view.findViewById(R.id.recipient_contact_text);
contactIcon = view.findViewById(R.id.recipient_contact_icon);
title = view.findViewById(R.id.name);
about = view.findViewById(R.id.about);
subtitle = view.findViewById(R.id.username_number);
internalDetails = view.findViewById(R.id.recipient_internal_details);
internalDetailsText = view.findViewById(R.id.recipient_internal_details_text);
disableProfileSharingButton = view.findViewById(R.id.recipient_internal_details_disable_profile_sharing_button);
sharedGroupList = view.findViewById(R.id.shared_group_list);
groupsInCommonCount = view.findViewById(R.id.groups_in_common_count);
threadPhotoRailView = view.findViewById(R.id.recent_photos);
mediaCard = view.findViewById(R.id.recipient_media_card);
sharedMediaRow = view.findViewById(R.id.shared_media_row);
disappearingMessagesCard = view.findViewById(R.id.recipient_disappearing_messages_card);
disappearingMessagesRow = view.findViewById(R.id.disappearing_messages_row);
disappearingMessages = view.findViewById(R.id.disappearing_messages);
blockUnblockCard = view.findViewById(R.id.recipient_block_and_leave_card);
block = view.findViewById(R.id.block);
unblock = view.findViewById(R.id.unblock);
viewSafetyNumber = view.findViewById(R.id.view_safety_number);
groupMembershipCard = view.findViewById(R.id.recipient_membership_card);
addToAGroup = view.findViewById(R.id.add_to_a_group);
muteNotificationsUntilLabel = view.findViewById(R.id.recipient_mute_notifications_until);
muteNotificationsSwitch = view.findViewById(R.id.recipient_mute_notifications_switch);
muteNotificationsRow = view.findViewById(R.id.recipient_mute_notifications_row);
notificationsCard = view.findViewById(R.id.recipient_notifications_card);
customNotificationsButton = view.findViewById(R.id.recipient_custom_notifications_button);
customNotificationsRow = view.findViewById(R.id.recipient_custom_notifications_row);
toggleAllGroups = view.findViewById(R.id.toggle_all_groups);
messageButton = view.findViewById(R.id.recipient_message);
secureCallButton = view.findViewById(R.id.recipient_voice_call);
insecureCallButton = view.findViewById(R.id.recipient_insecure_voice_call);
secureVideoCallButton = view.findViewById(R.id.recipient_video_call);
chatWallpaperButton = view.findViewById(R.id.chat_wallpaper);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
RecipientId recipientId = Objects.requireNonNull(requireArguments().getParcelable(RECIPIENT_ID));
boolean fromConversation = requireArguments().getBoolean(FROM_CONVERSATION, false);
ManageRecipientViewModel.Factory factory = new ManageRecipientViewModel.Factory(recipientId);
viewModel = ViewModelProviders.of(requireActivity(), factory).get(ManageRecipientViewModel.class);
viewModel.getCanCollapseMemberList().observe(getViewLifecycleOwner(), canCollapseMemberList -> {
if (canCollapseMemberList) {
toggleAllGroups.setVisibility(View.VISIBLE);
toggleAllGroups.setOnClickListener(v -> viewModel.revealCollapsedMembers());
} else {
toggleAllGroups.setVisibility(View.GONE);
}
});
viewModel.getIdentity().observe(getViewLifecycleOwner(), identityRecord -> {
viewSafetyNumber.setVisibility(identityRecord != null ? View.VISIBLE : View.GONE);
if (identityRecord != null) {
viewSafetyNumber.setOnClickListener(view -> viewModel.onViewSafetyNumberClicked(requireActivity(), identityRecord));
}
});
toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
toolbar.setOnMenuItemClickListener(this::onMenuItemSelected);
toolbar.inflateMenu(R.menu.manage_recipient_fragment);
if (recipientId.equals(Recipient.self().getId())) {
notificationsCard.setVisibility(View.GONE);
groupMembershipCard.setVisibility(View.GONE);
blockUnblockCard.setVisibility(View.GONE);
contactRow.setVisibility(View.GONE);
} else {
viewModel.getVisibleSharedGroups().observe(getViewLifecycleOwner(), members -> sharedGroupList.setMembers(members));
viewModel.getSharedGroupsCountSummary().observe(getViewLifecycleOwner(), members -> groupsInCommonCount.setText(members));
addToAGroup.setOnClickListener(v -> viewModel.onAddToGroupButton(requireActivity()));
sharedGroupList.setRecipientClickListener(recipient -> viewModel.onGroupClicked(requireActivity(), recipient));
sharedGroupList.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
viewModel.getTitle().observe(getViewLifecycleOwner(), title::setText);
viewModel.getSubtitle().observe(getViewLifecycleOwner(), text -> {
subtitle.setText(text);
subtitle.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
subtitle.setOnLongClickListener(null);
title.setOnLongClickListener(null);
setCopyToClipboardOnLongPress(TextUtils.isEmpty(text) ? title : subtitle);
});
viewModel.getDisappearingMessageTimer().observe(getViewLifecycleOwner(), string -> disappearingMessages.setText(string));
viewModel.getRecipient().observe(getViewLifecycleOwner(), this::presentRecipient);
viewModel.getMediaCursor().observe(getViewLifecycleOwner(), this::presentMediaCursor);
viewModel.getMuteState().observe(getViewLifecycleOwner(), this::presentMuteState);
viewModel.getCanAddToAGroup().observe(getViewLifecycleOwner(), canAdd -> addToAGroup.setVisibility(canAdd ? View.VISIBLE : View.GONE));
if (SignalStore.internalValues().recipientDetails()) {
viewModel.getInternalDetails().observe(getViewLifecycleOwner(), internalDetailsText::setText);
disableProfileSharingButton.setOnClickListener(v -> {
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getRecipientDatabase(requireContext()).setProfileSharing(recipientId, false));
});
internalDetails.setVisibility(View.VISIBLE);
} else {
internalDetails.setVisibility(View.GONE);
}
disappearingMessagesRow.setOnClickListener(v -> startActivity(RecipientDisappearingMessagesActivity.forRecipient(requireContext(), recipientId)));
block.setOnClickListener(v -> viewModel.onBlockClicked(requireActivity()));
unblock.setOnClickListener(v -> viewModel.onUnblockClicked(requireActivity()));
muteNotificationsRow.setOnClickListener(v -> {
if (muteNotificationsSwitch.isEnabled()) {
muteNotificationsSwitch.toggle();
}
});
customNotificationsRow.setVisibility(View.VISIBLE);
customNotificationsRow.setOnClickListener(v -> CustomNotificationsDialogFragment.create(recipientId)
.show(requireFragmentManager(), "CUSTOM_NOTIFICATIONS"));
//noinspection CodeBlock2Expr
if (NotificationChannels.supported()) {
viewModel.hasCustomNotifications().observe(getViewLifecycleOwner(), hasCustomNotifications -> {
customNotificationsButton.setText(hasCustomNotifications ? R.string.ManageRecipientActivity_on
: R.string.ManageRecipientActivity_off);
});
}
viewModel.getCanBlock().observe(getViewLifecycleOwner(),
canBlock -> block.setVisibility(canBlock ? View.VISIBLE : View.GONE));
viewModel.getCanUnblock().observe(getViewLifecycleOwner(),
canUnblock -> unblock.setVisibility(canUnblock ? View.VISIBLE : View.GONE));
messageButton.setOnClickListener(v -> {
if (fromConversation) {
requireActivity().onBackPressed();
} else {
viewModel.onMessage(requireActivity());
}
});
secureCallButton.setOnClickListener(v -> viewModel.onSecureCall(requireActivity()));
insecureCallButton.setOnClickListener(v -> viewModel.onInsecureCall(requireActivity()));
secureVideoCallButton.setOnClickListener(v -> viewModel.onSecureVideoCall(requireActivity()));
chatWallpaperButton.setOnClickListener(v -> startActivity(ChatWallpaperActivity.createIntent(requireContext(), recipientId)));
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_RETURN_FROM_MEDIA) {
applyMediaCursorFactory();
} else if (requestCode == REQUEST_CODE_ADD_CONTACT) {
viewModel.onAddedToContacts();
} else if (requestCode == REQUEST_CODE_VIEW_CONTACT) {
viewModel.onFinishedViewingContact();
}
}
private void presentRecipient(@NonNull Recipient recipient) {
Drawable colorCircle = recipient.getChatColors().asCircle();
colorCircle.setBounds(0, 0, ViewUtil.dpToPx(16), ViewUtil.dpToPx(16));
TextViewCompat.setCompoundDrawablesRelative(chatWallpaperButton, null, null, colorCircle, null);
if (recipient.isSystemContact()) {
contactText.setText(R.string.ManageRecipientActivity_this_person_is_in_your_contacts);
contactIcon.setVisibility(View.VISIBLE);
contactRow.setOnClickListener(v -> {
startActivityForResult(new Intent(Intent.ACTION_VIEW, recipient.getContactUri()), REQUEST_CODE_VIEW_CONTACT);
});
} else {
contactText.setText(R.string.ManageRecipientActivity_add_to_system_contacts);
contactIcon.setVisibility(View.GONE);
contactRow.setOnClickListener(v -> {
startActivityForResult(RecipientExporter.export(recipient).asAddContactIntent(), REQUEST_CODE_ADD_CONTACT);
});
}
String aboutText = recipient.getCombinedAboutAndEmoji();
about.setText(aboutText);
about.setVisibility(Util.isEmpty(aboutText) ? View.GONE : View.VISIBLE);
disappearingMessagesCard.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);
addToAGroup.setVisibility(recipient.isRegistered() ? View.VISIBLE : View.GONE);
AvatarColor recipientColor = recipient.getAvatarColor();
avatar.setFallbackPhotoProvider(new Recipient.FallbackPhotoProvider() {
@Override
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
return new FallbackPhoto80dp(R.drawable.ic_profile_80, recipientColor.colorInt());
}
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return new FallbackPhoto80dp(R.drawable.ic_note_80, recipientColor.colorInt());
}
});
avatar.setAvatar(recipient);
avatar.setOnClickListener(v -> {
FragmentActivity activity = requireActivity();
activity.startActivity(AvatarPreviewActivity.intentFromRecipientId(activity, recipient.getId()),
AvatarPreviewActivity.createTransitionBundle(activity, avatar));
});
secureCallButton.setVisibility(recipient.isRegistered() && !recipient.isSelf() ? View.VISIBLE : View.GONE);
insecureCallButton.setVisibility(!recipient.isRegistered() && !recipient.isSelf() ? View.VISIBLE : View.GONE);
secureVideoCallButton.setVisibility(recipient.isRegistered() && !recipient.isSelf() ? View.VISIBLE : View.GONE);
}
private void presentMediaCursor(ManageRecipientViewModel.MediaCursor mediaCursor) {
if (mediaCursor == null) return;
sharedMediaRow.setOnClickListener(v -> startActivity(MediaOverviewActivity.forThread(requireContext(), mediaCursor.getThreadId())));
setMediaCursorFactory(mediaCursor.getMediaCursorFactory());
threadPhotoRailView.setListener(mediaRecord ->
startActivityForResult(MediaPreviewActivity.intentFromMediaRecord(requireContext(),
mediaRecord,
ViewUtil.isLtr(threadPhotoRailView)),
REQUEST_CODE_RETURN_FROM_MEDIA));
}
private void presentMuteState(@NonNull ManageRecipientViewModel.MuteState muteState) {
if (muteNotificationsSwitch.isChecked() != muteState.isMuted()) {
muteNotificationsSwitch.setOnCheckedChangeListener(null);
muteNotificationsSwitch.setChecked(muteState.isMuted());
}
muteNotificationsSwitch.setEnabled(true);
muteNotificationsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
MuteDialog.show(requireContext(), viewModel::setMuteUntil, () -> muteNotificationsSwitch.setChecked(false));
} else {
viewModel.clearMuteUntil();
}
});
muteNotificationsUntilLabel.setVisibility(muteState.isMuted() ? View.VISIBLE : View.GONE);
if (muteState.isMuted()) {
if (muteState.getMutedUntil() == Long.MAX_VALUE) {
muteNotificationsUntilLabel.setText(R.string.ManageRecipientActivity_always);
} else {
muteNotificationsUntilLabel.setText(getString(R.string.ManageRecipientActivity_until_s,
DateUtils.getTimeString(requireContext(),
Locale.getDefault(),
muteState.getMutedUntil())));
}
}
}
public boolean onMenuItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_edit) {
startActivity(EditProfileActivity.getIntentForUserProfileEdit(requireActivity()));
return true;
}
return false;
}
private void setMediaCursorFactory(@Nullable ManageRecipientViewModel.CursorFactory cursorFactory) {
if (this.cursorFactory != cursorFactory) {
this.cursorFactory = cursorFactory;
applyMediaCursorFactory();
}
}
private void applyMediaCursorFactory() {
Context context = getContext();
if (context == null) return;
if (cursorFactory != null) {
Cursor cursor = cursorFactory.create();
getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleCursorWrapper(cursor));
threadPhotoRailView.setCursor(GlideApp.with(context), cursor);
mediaCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE);
} else {
threadPhotoRailView.setCursor(GlideApp.with(context), null);
mediaCard.setVisibility(View.GONE);
}
}
private static void setCopyToClipboardOnLongPress(@NonNull TextView textView) {
textView.setOnLongClickListener(v -> {
Util.copyToClipboard(v.getContext(), textView.getText().toString());
ServiceUtil.getVibrator(v.getContext()).vibrate(250);
Toast.makeText(v.getContext(), R.string.RecipientBottomSheet_copied_to_clipboard, Toast.LENGTH_SHORT).show();
return true;
});
}
}

View File

@@ -1,115 +0,0 @@
package org.thoughtcrime.securesms.recipients.ui.managerecipient;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import com.annimon.stream.Stream;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
final class ManageRecipientRepository {
private static final String TAG = Log.tag(ManageRecipientRepository.class);
private final Context context;
private final RecipientId recipientId;
ManageRecipientRepository(@NonNull Context context, @NonNull RecipientId recipientId) {
this.context = context;
this.recipientId = recipientId;
}
public RecipientId getRecipientId() {
return recipientId;
}
void getThreadId(@NonNull Consumer<Long> onGetThreadId) {
SignalExecutors.BOUNDED.execute(() -> onGetThreadId.accept(getThreadId()));
}
@WorkerThread
private long getThreadId() {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
Recipient groupRecipient = Recipient.resolved(recipientId);
return threadDatabase.getThreadIdFor(groupRecipient);
}
void getIdentity(@NonNull Consumer<IdentityDatabase.IdentityRecord> callback) {
SignalExecutors.BOUNDED.execute(() -> callback.accept(DatabaseFactory.getIdentityDatabase(context)
.getIdentity(recipientId)
.orNull()));
}
void getGroupMembership(@NonNull Consumer<List<RecipientId>> onComplete) {
SignalExecutors.BOUNDED.execute(() -> {
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
List<GroupDatabase.GroupRecord> groupRecords = groupDatabase.getPushGroupsContainingMember(recipientId);
ArrayList<RecipientId> groupRecipients = new ArrayList<>(groupRecords.size());
for (GroupDatabase.GroupRecord groupRecord : groupRecords) {
groupRecipients.add(groupRecord.getRecipientId());
}
onComplete.accept(groupRecipients);
});
}
public void getRecipient(@NonNull Consumer<Recipient> recipientCallback) {
SignalExecutors.BOUNDED.execute(() -> recipientCallback.accept(Recipient.resolved(recipientId)));
}
void setMuteUntil(long until) {
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getRecipientDatabase(context).setMuted(recipientId, until));
}
void refreshRecipient() {
SignalExecutors.UNBOUNDED.execute(() -> {
try {
DirectoryHelper.refreshDirectoryFor(context, Recipient.resolved(recipientId), false);
} catch (IOException e) {
Log.w(TAG, "Failed to refresh user after adding to contacts.");
}
});
}
@WorkerThread
@NonNull List<Recipient> getSharedGroups(@NonNull RecipientId recipientId) {
return Stream.of(DatabaseFactory.getGroupDatabase(context)
.getPushGroupsContainingMember(recipientId))
.filter(g -> g.getMembers().contains(Recipient.self().getId()))
.map(GroupDatabase.GroupRecord::getRecipientId)
.map(Recipient::resolved)
.sortBy(gr -> gr.getDisplayName(context))
.toList();
}
void getActiveGroupCount(@NonNull Consumer<Integer> onComplete) {
SignalExecutors.BOUNDED.execute(() -> onComplete.accept(DatabaseFactory.getGroupDatabase(context).getActiveGroupCount()));
}
@WorkerThread
boolean hasCustomNotifications(Recipient recipient) {
if (recipient.getNotificationChannel() != null || !NotificationChannels.supported()) {
return true;
}
return NotificationChannels.updateWithShortcutBasedChannel(context, recipient);
}
}

View File

@@ -1,361 +0,0 @@
package org.thoughtcrime.securesms.recipients.ui.managerecipient;
import android.app.Activity;
import android.app.NotificationChannel;
import android.content.Context;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
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 com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.VerifyIdentityActivity;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.List;
import java.util.UUID;
public final class ManageRecipientViewModel extends ViewModel {
private static final int MAX_UNCOLLAPSED_GROUPS = 6;
private static final int SHOW_COLLAPSED_GROUPS = 5;
private final Context context;
private final ManageRecipientRepository manageRecipientRepository;
private final LiveData<String> title;
private final LiveData<String> subtitle;
private final LiveData<String> internalDetails;
private final LiveData<String> disappearingMessageTimer;
private final MutableLiveData<IdentityDatabase.IdentityRecord> identity;
private final LiveData<Recipient> recipient;
private final MutableLiveData<MediaCursor> mediaCursor;
private final LiveData<MuteState> muteState;
private final LiveData<Boolean> hasCustomNotifications;
private final LiveData<Boolean> canCollapseMemberList;
private final DefaultValueLiveData<CollapseState> groupListCollapseState;
private final LiveData<Boolean> canBlock;
private final LiveData<Boolean> canUnblock;
private final LiveData<List<GroupMemberEntry.FullMember>> visibleSharedGroups;
private final LiveData<String> sharedGroupsCountSummary;
private final LiveData<Boolean> canAddToAGroup;
private ManageRecipientViewModel(@NonNull Context context, @NonNull ManageRecipientRepository manageRecipientRepository) {
this.context = context;
this.manageRecipientRepository = manageRecipientRepository;
this.recipient = Recipient.live(manageRecipientRepository.getRecipientId()).getLiveData();
this.title = Transformations.map(recipient, r -> getDisplayTitle(r, context) );
this.subtitle = Transformations.map(recipient, r -> getDisplaySubtitle(r, context));
this.identity = new MutableLiveData<>();
this.mediaCursor = new MutableLiveData<>(null);
this.groupListCollapseState = new DefaultValueLiveData<>(CollapseState.COLLAPSED);
this.disappearingMessageTimer = Transformations.map(this.recipient, r -> ExpirationUtil.getExpirationDisplayValue(context, r.getExpireMessages()));
this.muteState = Transformations.map(this.recipient, r -> new MuteState(r.getMuteUntil(), r.isMuted()));
this.hasCustomNotifications = LiveDataUtil.mapAsync(this.recipient, manageRecipientRepository::hasCustomNotifications);
this.canBlock = Transformations.map(this.recipient, r -> RecipientUtil.isBlockable(r) && !r.isBlocked());
this.canUnblock = Transformations.map(this.recipient, Recipient::isBlocked);
this.internalDetails = Transformations.map(this.recipient, this::populateInternalDetails);
manageRecipientRepository.getThreadId(this::onThreadIdLoaded);
LiveData<List<Recipient>> allSharedGroups = LiveDataUtil.mapAsync(this.recipient, r -> manageRecipientRepository.getSharedGroups(r.getId()));
this.sharedGroupsCountSummary = Transformations.map(allSharedGroups, list -> {
int size = list.size();
return size == 0 ? context.getString(R.string.ManageRecipientActivity_no_groups_in_common)
: context.getResources().getQuantityString(R.plurals.ManageRecipientActivity_d_groups_in_common, size, size);
});
this.canCollapseMemberList = LiveDataUtil.combineLatest(this.groupListCollapseState,
Transformations.map(allSharedGroups, m -> m.size() > MAX_UNCOLLAPSED_GROUPS),
(state, hasEnoughMembers) -> state != CollapseState.OPEN && hasEnoughMembers);
this.visibleSharedGroups = Transformations.map(LiveDataUtil.combineLatest(allSharedGroups,
this.groupListCollapseState,
ManageRecipientViewModel::filterSharedGroupList),
recipients -> Stream.of(recipients).map(r -> new GroupMemberEntry.FullMember(r, false)).toList());
boolean isSelf = manageRecipientRepository.getRecipientId().equals(Recipient.self().getId());
if (!isSelf) {
manageRecipientRepository.getIdentity(identity::postValue);
}
MutableLiveData<Integer> localGroupCount = new MutableLiveData<>(0);
this.canAddToAGroup = LiveDataUtil.combineLatest(recipient,
localGroupCount,
(r, count) -> count > 0 && r.isRegistered() && !r.isGroup() && !r.isSelf());
manageRecipientRepository.getActiveGroupCount(localGroupCount::postValue);
}
private static @NonNull String getDisplayTitle(@NonNull Recipient recipient, @NonNull Context context) {
if (recipient.isSelf()) {
return context.getString(R.string.note_to_self);
} else {
return recipient.getDisplayName(context);
}
}
private static @NonNull String getDisplaySubtitle(@NonNull Recipient recipient, @NonNull Context context) {
if (!recipient.isSelf() && recipient.hasAUserSetDisplayName(context)) {
return recipient.getSmsAddress().transform(PhoneNumberFormatter::prettyPrint).or("").trim();
} else {
return "";
}
}
@WorkerThread
private void onThreadIdLoaded(long threadId) {
mediaCursor.postValue(new MediaCursor(threadId,
() -> new ThreadMediaLoader(context, threadId, MediaLoader.MediaType.GALLERY, MediaDatabase.Sorting.Newest).getCursor()));
}
LiveData<String> getTitle() {
return title;
}
LiveData<String> getSubtitle() {
return subtitle;
}
LiveData<String> getInternalDetails() {
return internalDetails;
}
LiveData<Recipient> getRecipient() {
return recipient;
}
LiveData<Boolean> getCanAddToAGroup() {
return canAddToAGroup;
}
LiveData<MediaCursor> getMediaCursor() {
return mediaCursor;
}
LiveData<MuteState> getMuteState() {
return muteState;
}
LiveData<String> getDisappearingMessageTimer() {
return disappearingMessageTimer;
}
LiveData<Boolean> hasCustomNotifications() {
return hasCustomNotifications;
}
LiveData<Boolean> getCanCollapseMemberList() {
return canCollapseMemberList;
}
LiveData<Boolean> getCanBlock() {
return canBlock;
}
LiveData<Boolean> getCanUnblock() {
return canUnblock;
}
void setMuteUntil(long muteUntil) {
manageRecipientRepository.setMuteUntil(muteUntil);
}
void clearMuteUntil() {
manageRecipientRepository.setMuteUntil(0);
}
void revealCollapsedMembers() {
groupListCollapseState.setValue(CollapseState.OPEN);
}
void onAddToGroupButton(@NonNull Activity activity) {
manageRecipientRepository.getGroupMembership(existingGroups -> ThreadUtil.runOnMain(() -> activity.startActivity(AddToGroupsActivity.newIntent(activity, manageRecipientRepository.getRecipientId(), existingGroups))));
}
private void withRecipient(@NonNull Consumer<Recipient> mainThreadRecipientCallback) {
manageRecipientRepository.getRecipient(recipient -> ThreadUtil.runOnMain(() -> mainThreadRecipientCallback.accept(recipient)));
}
private static @NonNull List<Recipient> filterSharedGroupList(@NonNull List<Recipient> groups,
@NonNull CollapseState collapseState)
{
if (collapseState == CollapseState.COLLAPSED && groups.size() > MAX_UNCOLLAPSED_GROUPS) {
return groups.subList(0, SHOW_COLLAPSED_GROUPS);
} else {
return groups;
}
}
LiveData<IdentityDatabase.IdentityRecord> getIdentity() {
return identity;
}
void onBlockClicked(@NonNull FragmentActivity activity) {
withRecipient(recipient -> BlockUnblockDialog.showBlockFor(activity, activity.getLifecycle(), recipient, () -> RecipientUtil.blockNonGroup(context, recipient)));
}
void onUnblockClicked(@NonNull FragmentActivity activity) {
withRecipient(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));
}
LiveData<List<GroupMemberEntry.FullMember>> getVisibleSharedGroups() {
return visibleSharedGroups;
}
LiveData<String> getSharedGroupsCountSummary() {
return sharedGroupsCountSummary;
}
void onGroupClicked(@NonNull Activity activity, @NonNull Recipient recipient) {
CommunicationActions.startConversation(activity, recipient, null);
activity.finish();
}
void onMessage(@NonNull FragmentActivity activity) {
withRecipient(r -> {
CommunicationActions.startConversation(activity, r, null);
activity.finish();
});
}
void onSecureCall(@NonNull FragmentActivity activity) {
withRecipient(r -> CommunicationActions.startVoiceCall(activity, r));
}
void onInsecureCall(@NonNull FragmentActivity activity) {
withRecipient(r -> CommunicationActions.startInsecureCall(activity, r));
}
void onSecureVideoCall(@NonNull FragmentActivity activity) {
withRecipient(r -> CommunicationActions.startVideoCall(activity, r));
}
void onAddedToContacts() {
manageRecipientRepository.refreshRecipient();
}
void onFinishedViewingContact() {
manageRecipientRepository.refreshRecipient();
}
private @NonNull String populateInternalDetails(@NonNull Recipient recipient) {
if (!SignalStore.internalValues().recipientDetails()) {
return "";
}
String profileKeyBase64 = recipient.getProfileKey() != null ? Base64.encodeBytes(recipient.getProfileKey()) : "None";
String profileKeyHex = recipient.getProfileKey() != null ? Hex.toStringCondensed(recipient.getProfileKey()) : "None";
return String.format("-- Profile Name --\n[%s] [%s]\n\n" +
"-- Profile Sharing --\n%s\n\n" +
"-- Profile Key (Base64) --\n%s\n\n" +
"-- Profile Key (Hex) --\n%s\n\n" +
"-- Sealed Sender Mode --\n%s\n\n" +
"-- UUID --\n%s\n\n" +
"-- RecipientId --\n%s",
recipient.getProfileName().getGivenName(), recipient.getProfileName().getFamilyName(),
recipient.isProfileSharing(),
profileKeyBase64,
profileKeyHex,
recipient.getUnidentifiedAccessMode(),
recipient.getUuid().transform(UUID::toString).or("None"),
recipient.getId().serialize());
}
static final class MediaCursor {
private final long threadId;
@NonNull private final CursorFactory mediaCursorFactory;
private MediaCursor(long threadId,
@NonNull CursorFactory mediaCursorFactory)
{
this.threadId = threadId;
this.mediaCursorFactory = mediaCursorFactory;
}
long getThreadId() {
return threadId;
}
@NonNull CursorFactory getMediaCursorFactory() {
return mediaCursorFactory;
}
}
static final class MuteState {
private final long mutedUntil;
private final boolean isMuted;
MuteState(long mutedUntil, boolean isMuted) {
this.mutedUntil = mutedUntil;
this.isMuted = isMuted;
}
long getMutedUntil() {
return mutedUntil;
}
public boolean isMuted() {
return isMuted;
}
}
private enum CollapseState {
OPEN,
COLLAPSED
}
interface CursorFactory {
Cursor create();
}
public static class Factory implements ViewModelProvider.Factory {
private final Context context;
private final RecipientId recipientId;
public Factory(@NonNull RecipientId recipientId) {
this.context = ApplicationDependencies.getApplication();
this.recipientId = recipientId;
}
@Override
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection unchecked
return (T) new ManageRecipientViewModel(context, new ManageRecipientRepository(context, recipientId));
}
}
}