mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Refactor conversation settings screens into a single fragment with new UI.
This commit is contained in:
committed by
Cody Henthorne
parent
f19033a7a2
commit
da2ee33dff
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user