mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +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
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -76,7 +77,7 @@ public final class LeaveGroupDialog {
|
||||
}
|
||||
|
||||
private void showSelectNewAdminDialog() {
|
||||
new AlertDialog.Builder(activity)
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.LeaveGroupDialog_choose_new_admin)
|
||||
.setMessage(R.string.LeaveGroupDialog_before_you_leave_you_must_choose_at_least_one_new_admin_for_this_group)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
@@ -85,7 +86,7 @@ public final class LeaveGroupDialog {
|
||||
}
|
||||
|
||||
private void showLeaveDialog() {
|
||||
new AlertDialog.Builder(activity)
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.LeaveGroupDialog_leave_group)
|
||||
.setCancelable(true)
|
||||
.setMessage(R.string.LeaveGroupDialog_you_will_no_longer_be_able_to_send_or_receive_messages_in_this_group)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.addmembers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
@@ -9,14 +11,20 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import org.thoughtcrime.securesms.ContactSelectionActivity;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.PushContactSelectionActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AddMembersActivity extends PushContactSelectionActivity {
|
||||
|
||||
public static final String GROUP_ID = "group_id";
|
||||
@@ -24,6 +32,22 @@ public class AddMembersActivity extends PushContactSelectionActivity {
|
||||
private View done;
|
||||
private AddMembersViewModel viewModel;
|
||||
|
||||
public static @NonNull Intent createIntent(@NonNull Context context,
|
||||
@NonNull GroupId groupId,
|
||||
int displayModeFlags,
|
||||
int selectionWarning,
|
||||
int selectionLimit,
|
||||
@NonNull List<RecipientId> membersWithoutSelf) {
|
||||
Intent intent = new Intent(context, AddMembersActivity.class);
|
||||
|
||||
intent.putExtra(AddMembersActivity.GROUP_ID, groupId.toString());
|
||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayModeFlags);
|
||||
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, new SelectionLimits(selectionWarning, selectionLimit));
|
||||
intent.putParcelableArrayListExtra(ContactSelectionListFragment.CURRENT_SELECTION, new ArrayList<>(membersWithoutSelf));
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle, boolean ready) {
|
||||
getIntent().putExtra(ContactSelectionActivity.EXTRA_LAYOUT_RES_ID, R.layout.add_members_activity);
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.managegroup;
|
||||
|
||||
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.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class ManageGroupActivity extends PassphraseRequiredActivity {
|
||||
|
||||
private static final String GROUP_ID = "GROUP_ID";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
public static Intent newIntent(@NonNull Context context, @NonNull GroupId groupId) {
|
||||
Intent intent = new Intent(context, ManageGroupActivity.class);
|
||||
intent.putExtra(GROUP_ID, groupId.toString());
|
||||
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.group_manage_activity);
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.container, ManageGroupFragment.newInstance(getIntent().getStringExtra(GROUP_ID)))
|
||||
.commitNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.managegroup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
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.CompoundButton;
|
||||
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 com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.AvatarPreviewActivity;
|
||||
import org.thoughtcrime.securesms.InviteActivity;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.MuteDialog;
|
||||
import org.thoughtcrime.securesms.PushContactSelectionActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
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.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupRightsDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupsLearnMoreBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInitiationBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupDescriptionUtil;
|
||||
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.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity;
|
||||
import org.thoughtcrime.securesms.recipients.ui.notifications.CustomNotificationsDialogFragment;
|
||||
import org.thoughtcrime.securesms.recipients.ui.sharablegrouplink.ShareableGroupLinkDialogFragment;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LifecycleCursorWrapper;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ManageGroupFragment extends LoggingFragment {
|
||||
private static final String GROUP_ID = "GROUP_ID";
|
||||
|
||||
private static final String TAG = Log.tag(ManageGroupFragment.class);
|
||||
|
||||
private static final int RETURN_FROM_MEDIA = 33114;
|
||||
private static final int PICK_CONTACT = 61341;
|
||||
public static final String DIALOG_TAG = "DIALOG";
|
||||
|
||||
private ManageGroupViewModel viewModel;
|
||||
private GroupMemberListView groupMemberList;
|
||||
private View pendingAndRequestingRow;
|
||||
private TextView pendingAndRequestingCount;
|
||||
private Toolbar toolbar;
|
||||
private TextView groupName;
|
||||
private EmojiTextView groupDescription;
|
||||
private LearnMoreTextView groupInfoText;
|
||||
private TextView memberCountUnderAvatar;
|
||||
private TextView memberCountAboveList;
|
||||
private AvatarImageView avatar;
|
||||
private ThreadPhotoRailView threadPhotoRailView;
|
||||
private View groupMediaCard;
|
||||
private View accessControlCard;
|
||||
private View groupLinkCard;
|
||||
private ManageGroupViewModel.CursorFactory cursorFactory;
|
||||
private View sharedMediaRow;
|
||||
private View editGroupAccessRow;
|
||||
private TextView editGroupAccessValue;
|
||||
private View editGroupMembershipRow;
|
||||
private TextView editGroupMembershipValue;
|
||||
private View disappearingMessagesCard;
|
||||
private View disappearingMessagesRow;
|
||||
private TextView disappearingMessages;
|
||||
private View blockAndLeaveCard;
|
||||
private TextView blockGroup;
|
||||
private TextView unblockGroup;
|
||||
private TextView leaveGroup;
|
||||
private TextView addMembers;
|
||||
private SwitchCompat muteNotificationsSwitch;
|
||||
private View muteNotificationsRow;
|
||||
private TextView muteNotificationsUntilLabel;
|
||||
private TextView customNotificationsButton;
|
||||
private View customNotificationsRow;
|
||||
private View mentionsRow;
|
||||
private TextView mentionsValue;
|
||||
private View toggleAllMembers;
|
||||
private View groupLinkRow;
|
||||
private TextView groupLinkButton;
|
||||
private TextView wallpaperButton;
|
||||
|
||||
static ManageGroupFragment newInstance(@NonNull String groupId) {
|
||||
ManageGroupFragment fragment = new ManageGroupFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putString(GROUP_ID, groupId);
|
||||
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.group_manage_fragment, container, false);
|
||||
|
||||
avatar = view.findViewById(R.id.group_avatar);
|
||||
toolbar = view.findViewById(R.id.toolbar);
|
||||
groupName = view.findViewById(R.id.name);
|
||||
groupDescription = view.findViewById(R.id.manage_group_description);
|
||||
groupInfoText = view.findViewById(R.id.manage_group_info_text);
|
||||
memberCountUnderAvatar = view.findViewById(R.id.member_count);
|
||||
memberCountAboveList = view.findViewById(R.id.member_count_2);
|
||||
groupMemberList = view.findViewById(R.id.group_members);
|
||||
pendingAndRequestingRow = view.findViewById(R.id.pending_and_requesting_members_row);
|
||||
pendingAndRequestingCount = view.findViewById(R.id.pending_and_requesting_members_count);
|
||||
threadPhotoRailView = view.findViewById(R.id.recent_photos);
|
||||
groupMediaCard = view.findViewById(R.id.group_media_card);
|
||||
accessControlCard = view.findViewById(R.id.group_access_control_card);
|
||||
groupLinkCard = view.findViewById(R.id.group_link_card);
|
||||
sharedMediaRow = view.findViewById(R.id.shared_media_row);
|
||||
editGroupAccessRow = view.findViewById(R.id.edit_group_access_row);
|
||||
editGroupAccessValue = view.findViewById(R.id.edit_group_access_value);
|
||||
editGroupMembershipRow = view.findViewById(R.id.edit_group_membership_row);
|
||||
editGroupMembershipValue = view.findViewById(R.id.edit_group_membership_value);
|
||||
disappearingMessagesCard = view.findViewById(R.id.group_disappearing_messages_card);
|
||||
disappearingMessagesRow = view.findViewById(R.id.disappearing_messages_row);
|
||||
disappearingMessages = view.findViewById(R.id.disappearing_messages);
|
||||
blockAndLeaveCard = view.findViewById(R.id.group_block_and_leave_card);
|
||||
blockGroup = view.findViewById(R.id.blockGroup);
|
||||
unblockGroup = view.findViewById(R.id.unblockGroup);
|
||||
leaveGroup = view.findViewById(R.id.leaveGroup);
|
||||
addMembers = view.findViewById(R.id.add_members);
|
||||
muteNotificationsUntilLabel = view.findViewById(R.id.group_mute_notifications_until);
|
||||
muteNotificationsSwitch = view.findViewById(R.id.group_mute_notifications_switch);
|
||||
muteNotificationsRow = view.findViewById(R.id.group_mute_notifications_row);
|
||||
customNotificationsButton = view.findViewById(R.id.group_custom_notifications_button);
|
||||
customNotificationsRow = view.findViewById(R.id.group_custom_notifications_row);
|
||||
mentionsRow = view.findViewById(R.id.group_mentions_row);
|
||||
mentionsValue = view.findViewById(R.id.group_mentions_value);
|
||||
toggleAllMembers = view.findViewById(R.id.toggle_all_members);
|
||||
groupLinkRow = view.findViewById(R.id.group_link_row);
|
||||
groupLinkButton = view.findViewById(R.id.group_link_button);
|
||||
wallpaperButton = view.findViewById(R.id.chat_wallpaper);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Context context = requireContext();
|
||||
GroupId groupId = getGroupId();
|
||||
ManageGroupViewModel.Factory factory = new ManageGroupViewModel.Factory(context, groupId);
|
||||
|
||||
disappearingMessagesCard.setVisibility(groupId.isPush() ? View.VISIBLE : View.GONE);
|
||||
blockAndLeaveCard.setVisibility(groupId.isPush() ? View.VISIBLE : View.GONE);
|
||||
|
||||
viewModel = ViewModelProviders.of(requireActivity(), factory).get(ManageGroupViewModel.class);
|
||||
|
||||
viewModel.getMembers().observe(getViewLifecycleOwner(), members -> groupMemberList.setMembers(members));
|
||||
|
||||
viewModel.getCanCollapseMemberList().observe(getViewLifecycleOwner(), canCollapseMemberList -> {
|
||||
if (canCollapseMemberList) {
|
||||
toggleAllMembers.setVisibility(View.VISIBLE);
|
||||
toggleAllMembers.setOnClickListener(v -> viewModel.revealCollapsedMembers());
|
||||
} else {
|
||||
toggleAllMembers.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getPendingAndRequestingCount().observe(getViewLifecycleOwner(), pendingAndRequestingCount -> {
|
||||
pendingAndRequestingRow.setOnClickListener(v -> {
|
||||
FragmentActivity activity = requireActivity();
|
||||
activity.startActivity(ManagePendingAndRequestingMembersActivity.newIntent(activity, groupId.requireV2()));
|
||||
});
|
||||
if (pendingAndRequestingCount == 0) {
|
||||
this.pendingAndRequestingCount.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.pendingAndRequestingCount.setText(String.format(Locale.getDefault(), "%d", pendingAndRequestingCount));
|
||||
this.pendingAndRequestingCount.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
|
||||
toolbar.setOnMenuItemClickListener(this::onMenuItemSelected);
|
||||
toolbar.inflateMenu(R.menu.manage_group_fragment);
|
||||
|
||||
viewModel.getCanEditGroupAttributes().observe(getViewLifecycleOwner(), canEdit -> {
|
||||
toolbar.getMenu().findItem(R.id.action_edit).setVisible(canEdit);
|
||||
disappearingMessages.setEnabled(canEdit);
|
||||
disappearingMessagesRow.setEnabled(canEdit);
|
||||
});
|
||||
|
||||
viewModel.getTitle().observe(getViewLifecycleOwner(), groupName::setText);
|
||||
viewModel.getDescription().observe(getViewLifecycleOwner(), this::updateGroupDescription);
|
||||
viewModel.getMemberCountSummary().observe(getViewLifecycleOwner(), memberCountUnderAvatar::setText);
|
||||
viewModel.getFullMemberCountSummary().observe(getViewLifecycleOwner(), memberCountAboveList::setText);
|
||||
viewModel.getGroupRecipient().observe(getViewLifecycleOwner(), groupRecipient -> {
|
||||
avatar.setFallbackPhotoProvider(new FallbackPhotoProvider(groupRecipient.getAvatarColor()));
|
||||
avatar.setRecipient(groupRecipient);
|
||||
avatar.setOnClickListener(v -> {
|
||||
FragmentActivity activity = requireActivity();
|
||||
activity.startActivity(AvatarPreviewActivity.intentFromRecipientId(activity, groupRecipient.getId()),
|
||||
AvatarPreviewActivity.createTransitionBundle(activity, avatar));
|
||||
});
|
||||
customNotificationsRow.setOnClickListener(v -> CustomNotificationsDialogFragment.create(groupRecipient.getId())
|
||||
.show(requireFragmentManager(), DIALOG_TAG));
|
||||
wallpaperButton.setOnClickListener(v -> startActivity(ChatWallpaperActivity.createIntent(requireContext(), groupRecipient.getId())));
|
||||
|
||||
Drawable colorCircle = groupRecipient.getChatColors().asCircle();
|
||||
colorCircle.setBounds(0, 0, ViewUtil.dpToPx(16), ViewUtil.dpToPx(16));
|
||||
TextViewCompat.setCompoundDrawablesRelative(wallpaperButton, null, null, colorCircle, null);
|
||||
});
|
||||
|
||||
if (groupId.isV2()) {
|
||||
groupLinkRow.setOnClickListener(v -> ShareableGroupLinkDialogFragment.create(groupId.requireV2())
|
||||
.show(requireFragmentManager(), DIALOG_TAG));
|
||||
viewModel.getGroupLinkOn().observe(getViewLifecycleOwner(), linkEnabled -> groupLinkButton.setText(booleanToOnOff(linkEnabled)));
|
||||
}
|
||||
|
||||
viewModel.getGroupViewState().observe(getViewLifecycleOwner(), vs -> {
|
||||
if (vs == null) return;
|
||||
sharedMediaRow.setOnClickListener(v -> startActivity(MediaOverviewActivity.forThread(context, vs.getThreadId())));
|
||||
|
||||
setMediaCursorFactory(vs.getMediaCursorFactory());
|
||||
|
||||
threadPhotoRailView.setListener(mediaRecord ->
|
||||
startActivityForResult(MediaPreviewActivity.intentFromMediaRecord(context,
|
||||
mediaRecord,
|
||||
ViewUtil.isLtr(threadPhotoRailView)),
|
||||
RETURN_FROM_MEDIA));
|
||||
|
||||
groupLinkCard.setVisibility(vs.getGroupRecipient().requireGroupId().isV2() ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
|
||||
leaveGroup.setVisibility(groupId.isPush() ? View.VISIBLE : View.GONE);
|
||||
leaveGroup.setOnClickListener(v -> LeaveGroupDialog.handleLeavePushGroup(requireActivity(), groupId.requirePush(), () -> startActivity(MainActivity.clearTop(context))));
|
||||
|
||||
viewModel.getDisappearingMessageTimer().observe(getViewLifecycleOwner(), string -> disappearingMessages.setText(string));
|
||||
|
||||
disappearingMessagesRow.setOnClickListener(v -> {
|
||||
Recipient recipient = viewModel.getGroupRecipient().getValue();
|
||||
if (recipient != null) {
|
||||
startActivity(RecipientDisappearingMessagesActivity.forRecipient(requireContext(), recipient.getId()));
|
||||
}
|
||||
});
|
||||
blockGroup.setOnClickListener(v -> viewModel.blockAndLeave(requireActivity()));
|
||||
unblockGroup.setOnClickListener(v -> viewModel.unblock(requireActivity()));
|
||||
|
||||
addMembers.setOnClickListener(v -> viewModel.onAddMembersClick(this, PICK_CONTACT));
|
||||
|
||||
viewModel.getMembershipRights().observe(getViewLifecycleOwner(), r -> {
|
||||
if (r != null) {
|
||||
editGroupMembershipValue.setText(r.getString());
|
||||
editGroupMembershipRow.setOnClickListener(v -> new GroupRightsDialog(context, GroupRightsDialog.Type.MEMBERSHIP, r, (from, to) -> viewModel.applyMembershipRightsChange(to)).show());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
viewModel.getEditGroupAttributesRights().observe(getViewLifecycleOwner(), r -> {
|
||||
if (r != null) {
|
||||
editGroupAccessValue.setText(r.getString());
|
||||
editGroupAccessRow.setOnClickListener(v -> new GroupRightsDialog(context, GroupRightsDialog.Type.ATTRIBUTES, r, (from, to) -> viewModel.applyAttributesRightsChange(to)).show());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
viewModel.getIsAdmin().observe(getViewLifecycleOwner(), admin -> {
|
||||
accessControlCard.setVisibility(admin ? View.VISIBLE : View.GONE);
|
||||
editGroupMembershipRow.setEnabled(admin);
|
||||
editGroupMembershipValue.setEnabled(admin);
|
||||
editGroupAccessRow.setEnabled(admin);
|
||||
editGroupAccessValue.setEnabled(admin);
|
||||
});
|
||||
|
||||
viewModel.getCanAddMembers().observe(getViewLifecycleOwner(), canEdit -> addMembers.setVisibility(canEdit ? View.VISIBLE : View.GONE));
|
||||
|
||||
groupMemberList.setRecipientClickListener(recipient -> RecipientBottomSheetDialogFragment.create(recipient.getId(), groupId).show(requireFragmentManager(), "BOTTOM"));
|
||||
groupMemberList.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
|
||||
final CompoundButton.OnCheckedChangeListener muteSwitchListener = (buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
MuteDialog.show(context, viewModel::setMuteUntil, () -> muteNotificationsSwitch.setChecked(false));
|
||||
} else {
|
||||
viewModel.clearMuteUntil();
|
||||
}
|
||||
};
|
||||
|
||||
muteNotificationsRow.setOnClickListener(v -> {
|
||||
if (muteNotificationsSwitch.isEnabled()) {
|
||||
muteNotificationsSwitch.toggle();
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getMuteState().observe(getViewLifecycleOwner(), muteState -> {
|
||||
if (muteNotificationsSwitch.isChecked() != muteState.isMuted()) {
|
||||
muteNotificationsSwitch.setOnCheckedChangeListener(null);
|
||||
muteNotificationsSwitch.setChecked(muteState.isMuted());
|
||||
}
|
||||
|
||||
muteNotificationsSwitch.setEnabled(true);
|
||||
muteNotificationsSwitch.setOnCheckedChangeListener(muteSwitchListener);
|
||||
muteNotificationsUntilLabel.setVisibility(muteState.isMuted() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (muteState.isMuted()) {
|
||||
if (muteState.getMutedUntil() == Long.MAX_VALUE) {
|
||||
muteNotificationsUntilLabel.setText(R.string.ManageGroupActivity_always);
|
||||
} else {
|
||||
muteNotificationsUntilLabel.setText(getString(R.string.ManageGroupActivity_until_s,
|
||||
DateUtils.getTimeString(requireContext(),
|
||||
Locale.getDefault(),
|
||||
muteState.getMutedUntil())));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
customNotificationsRow.setVisibility(View.VISIBLE);
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
viewModel.hasCustomNotifications().observe(getViewLifecycleOwner(), hasCustomNotifications -> {
|
||||
customNotificationsButton.setText(booleanToOnOff(hasCustomNotifications));
|
||||
});
|
||||
}
|
||||
|
||||
mentionsRow.setVisibility(groupId.isV2() ? View.VISIBLE : View.GONE);
|
||||
mentionsRow.setOnClickListener(v -> viewModel.handleMentionNotificationSelection());
|
||||
viewModel.getMentionSetting().observe(getViewLifecycleOwner(), value -> mentionsValue.setText(value));
|
||||
|
||||
viewModel.getCanLeaveGroup().observe(getViewLifecycleOwner(), canLeave -> leaveGroup.setVisibility(canLeave ? View.VISIBLE : View.GONE));
|
||||
viewModel.getCanBlockGroup().observe(getViewLifecycleOwner(), canBlock -> blockGroup.setVisibility(canBlock ? View.VISIBLE : View.GONE));
|
||||
viewModel.getCanUnblockGroup().observe(getViewLifecycleOwner(), canUnblock -> unblockGroup.setVisibility(canUnblock ? View.VISIBLE : View.GONE));
|
||||
|
||||
viewModel.getGroupInfoMessage().observe(getViewLifecycleOwner(), message -> {
|
||||
switch (message) {
|
||||
case LEGACY_GROUP_LEARN_MORE:
|
||||
groupInfoText.setText(R.string.ManageGroupActivity_legacy_group_learn_more);
|
||||
groupInfoText.setOnLinkClickListener(v -> GroupsLearnMoreBottomSheetDialogFragment.show(requireFragmentManager()));
|
||||
groupInfoText.setLearnMoreVisible(true);
|
||||
groupInfoText.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case LEGACY_GROUP_UPGRADE:
|
||||
groupInfoText.setText(R.string.ManageGroupActivity_legacy_group_upgrade);
|
||||
groupInfoText.setOnLinkClickListener(v -> GroupsV1MigrationInitiationBottomSheetDialogFragment.showForInitiation(requireFragmentManager(), Recipient.externalPossiblyMigratedGroup(requireContext(), groupId).getId()));
|
||||
groupInfoText.setLearnMoreVisible(true, R.string.ManageGroupActivity_upgrade_this_group);
|
||||
groupInfoText.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case LEGACY_GROUP_TOO_LARGE:
|
||||
groupInfoText.setText(context.getString(R.string.ManageGroupActivity_legacy_group_too_large, FeatureFlags.groupLimits().getHardLimit() - 1));
|
||||
groupInfoText.setLearnMoreVisible(false);
|
||||
groupInfoText.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case MMS_WARNING:
|
||||
groupInfoText.setText(R.string.ManageGroupActivity_this_is_an_insecure_mms_group);
|
||||
groupInfoText.setOnLinkClickListener(v -> startActivity(new Intent(requireContext(), InviteActivity.class)));
|
||||
groupInfoText.setLearnMoreVisible(true, R.string.ManageGroupActivity_invite_now);
|
||||
groupInfoText.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
default:
|
||||
groupInfoText.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static int booleanToOnOff(boolean isOn) {
|
||||
return isOn ? R.string.ManageGroupActivity_on
|
||||
: R.string.ManageGroupActivity_off;
|
||||
}
|
||||
|
||||
public boolean onMenuItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_edit) {
|
||||
startActivity(EditProfileActivity.getIntentForGroupProfile(requireActivity(), getGroupId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private GroupId getGroupId() {
|
||||
return GroupId.parseOrThrow(Objects.requireNonNull(requireArguments().getString(GROUP_ID)));
|
||||
}
|
||||
|
||||
private void setMediaCursorFactory(@Nullable ManageGroupViewModel.CursorFactory cursorFactory) {
|
||||
if (this.cursorFactory != cursorFactory) {
|
||||
this.cursorFactory = cursorFactory;
|
||||
applyMediaCursorFactory();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyMediaCursorFactory() {
|
||||
Context context = getContext();
|
||||
if (context == null) return;
|
||||
if (this.cursorFactory != null) {
|
||||
Cursor cursor = this.cursorFactory.create();
|
||||
getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleCursorWrapper(cursor));
|
||||
|
||||
threadPhotoRailView.setCursor(GlideApp.with(context), cursor);
|
||||
groupMediaCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
} else {
|
||||
threadPhotoRailView.setCursor(GlideApp.with(context), null);
|
||||
groupMediaCard.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGroupDescription(@NonNull ManageGroupViewModel.Description description) {
|
||||
if (!TextUtils.isEmpty(description.getDescription()) || description.canEditDescription()) {
|
||||
groupDescription.setVisibility(View.VISIBLE);
|
||||
groupDescription.setMovementMethod(LongClickMovementMethod.getInstance(requireContext()));
|
||||
memberCountUnderAvatar.setVisibility(View.GONE);
|
||||
} else {
|
||||
groupDescription.setVisibility(View.GONE);
|
||||
groupDescription.setMovementMethod(null);
|
||||
memberCountUnderAvatar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(description.getDescription())) {
|
||||
if (description.canEditDescription()) {
|
||||
groupDescription.setOverflowText(null);
|
||||
groupDescription.setText(R.string.ManageGroupActivity_add_group_description);
|
||||
groupDescription.setOnClickListener(v -> startActivity(EditProfileActivity.getIntentForGroupProfile(requireActivity(), getGroupId())));
|
||||
}
|
||||
} else {
|
||||
groupDescription.setOnClickListener(null);
|
||||
GroupDescriptionUtil.setText(requireContext(),
|
||||
groupDescription,
|
||||
description.getDescription(),
|
||||
description.shouldLinkifyWebLinks(),
|
||||
() -> GroupDescriptionDialog.show(getChildFragmentManager(), getGroupId(), null, description.shouldLinkifyWebLinks()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == RETURN_FROM_MEDIA) {
|
||||
applyMediaCursorFactory();
|
||||
} else if (requestCode == PICK_CONTACT && data != null) {
|
||||
List<RecipientId> selected = data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS);
|
||||
SimpleProgressDialog.DismissibleDialog progress = SimpleProgressDialog.showDelayed(requireContext());
|
||||
|
||||
viewModel.onAddMembers(selected, new AsynchronousCallback.MainThread<ManageGroupViewModel.AddMembersResult, GroupChangeFailureReason>() {
|
||||
@Override
|
||||
public void onComplete(ManageGroupViewModel.AddMembersResult result) {
|
||||
progress.dismiss();
|
||||
if (!result.getNewInvitedMembers().isEmpty()) {
|
||||
GroupInviteSentDialog.showInvitesSent(requireContext(), result.getNewInvitedMembers());
|
||||
}
|
||||
|
||||
if (result.getNumberOfMembersAdded() > 0) {
|
||||
String string = getResources().getQuantityString(R.plurals.ManageGroupActivity_added,
|
||||
result.getNumberOfMembersAdded(),
|
||||
result.getNumberOfMembersAdded());
|
||||
Snackbar.make(requireView(), string, Snackbar.LENGTH_SHORT).setTextColor(Color.WHITE).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable GroupChangeFailureReason error) {
|
||||
progress.dismiss();
|
||||
Toast.makeText(requireContext(), GroupErrors.getUserDisplayMessage(error), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
|
||||
|
||||
private final AvatarColor groupColors;
|
||||
|
||||
private FallbackPhotoProvider(@NonNull AvatarColor groupColors) {
|
||||
this.groupColors = groupColors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull FallbackContactPhoto getPhotoForGroup() {
|
||||
return new FallbackPhoto80dp(R.drawable.ic_group_80, groupColors.colorInt());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.managegroup;
|
||||
|
||||
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.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupAccessControl;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
|
||||
import org.thoughtcrime.securesms.groups.MembershipNotSuitableForV2Exception;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeErrorCallback;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
final class ManageGroupRepository {
|
||||
|
||||
private static final String TAG = Log.tag(ManageGroupRepository.class);
|
||||
|
||||
private final Context context;
|
||||
|
||||
ManageGroupRepository(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void getGroupState(@NonNull GroupId groupId, @NonNull Consumer<GroupStateResult> onGroupStateLoaded) {
|
||||
SignalExecutors.BOUNDED.execute(() -> onGroupStateLoaded.accept(getGroupState(groupId)));
|
||||
}
|
||||
|
||||
void getGroupCapacity(@NonNull GroupId groupId, @NonNull Consumer<GroupCapacityResult> onGroupCapacityLoaded) {
|
||||
SimpleTask.run(SignalExecutors.BOUNDED, () -> {
|
||||
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).getGroup(groupId).get();
|
||||
if (groupRecord.isV2Group()) {
|
||||
DecryptedGroup decryptedGroup = groupRecord.requireV2GroupProperties().getDecryptedGroup();
|
||||
List<RecipientId> pendingMembers = Stream.of(decryptedGroup.getPendingMembersList())
|
||||
.map(member -> GroupProtoUtil.uuidByteStringToRecipientId(member.getUuid()))
|
||||
.toList();
|
||||
List<RecipientId> members = new LinkedList<>(groupRecord.getMembers());
|
||||
|
||||
members.addAll(pendingMembers);
|
||||
|
||||
return new GroupCapacityResult(members, FeatureFlags.groupLimits());
|
||||
} else {
|
||||
return new GroupCapacityResult(groupRecord.getMembers(), FeatureFlags.groupLimits());
|
||||
}
|
||||
}, onGroupCapacityLoaded::accept);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private GroupStateResult getGroupState(@NonNull GroupId groupId) {
|
||||
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
Recipient groupRecipient = Recipient.externalGroupExact(context, groupId);
|
||||
long threadId = threadDatabase.getThreadIdFor(groupRecipient);
|
||||
|
||||
return new GroupStateResult(threadId, groupRecipient);
|
||||
}
|
||||
|
||||
void applyMembershipRightsChange(@NonNull GroupId groupId, @NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.applyMembershipAdditionRightsChange(context, groupId.requireV2(), newRights);
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
error.onError(GroupChangeFailureReason.fromException(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void applyAttributesRightsChange(@NonNull GroupId groupId, @NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.applyAttributesRightsChange(context, groupId.requireV2(), newRights);
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
error.onError(GroupChangeFailureReason.fromException(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void getRecipient(@NonNull GroupId groupId, @NonNull Consumer<Recipient> recipientCallback) {
|
||||
SimpleTask.run(SignalExecutors.BOUNDED,
|
||||
() -> Recipient.externalGroupExact(context, groupId),
|
||||
recipientCallback::accept);
|
||||
}
|
||||
|
||||
void setMuteUntil(@NonNull GroupId groupId, long until) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
RecipientId recipientId = Recipient.externalGroupExact(context, groupId).getId();
|
||||
DatabaseFactory.getRecipientDatabase(context).setMuted(recipientId, until);
|
||||
});
|
||||
}
|
||||
|
||||
void addMembers(@NonNull GroupId groupId,
|
||||
@NonNull List<RecipientId> selected,
|
||||
@NonNull AsynchronousCallback.WorkerThread<ManageGroupViewModel.AddMembersResult, GroupChangeFailureReason> callback)
|
||||
{
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.GroupActionResult groupActionResult = GroupManager.addMembers(context, groupId.requirePush(), selected);
|
||||
callback.onComplete(new ManageGroupViewModel.AddMembersResult(groupActionResult.getAddedMemberCount(), Recipient.resolvedList(groupActionResult.getInvitedMembers())));
|
||||
} catch (GroupChangeException | MembershipNotSuitableForV2Exception | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
callback.onError(GroupChangeFailureReason.fromException(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void blockAndLeaveGroup(@NonNull GroupId groupId, @NonNull GroupChangeErrorCallback error, @NonNull Runnable onSuccess) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
RecipientUtil.block(context, Recipient.externalGroupExact(context, groupId));
|
||||
onSuccess.run();
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
error.onError(GroupChangeFailureReason.fromException(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setMentionSetting(@NonNull GroupId groupId, RecipientDatabase.MentionSetting mentionSetting) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
RecipientId recipientId = Recipient.externalGroupExact(context, groupId).getId();
|
||||
DatabaseFactory.getRecipientDatabase(context).setMentionSetting(recipientId, mentionSetting);
|
||||
});
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
boolean hasCustomNotifications(Recipient recipient) {
|
||||
if (recipient.getNotificationChannel() != null || !NotificationChannels.supported()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return NotificationChannels.updateWithShortcutBasedChannel(context, recipient);
|
||||
}
|
||||
|
||||
static final class GroupStateResult {
|
||||
|
||||
private final long threadId;
|
||||
private final Recipient recipient;
|
||||
|
||||
private GroupStateResult(long threadId,
|
||||
Recipient recipient)
|
||||
{
|
||||
this.threadId = threadId;
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
long getThreadId() {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
Recipient getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
}
|
||||
|
||||
static final class GroupCapacityResult {
|
||||
private final List<RecipientId> members;
|
||||
private final SelectionLimits selectionLimits;
|
||||
|
||||
GroupCapacityResult(@NonNull List<RecipientId> members, @NonNull SelectionLimits selectionLimits) {
|
||||
this.members = members;
|
||||
this.selectionLimits = selectionLimits;
|
||||
}
|
||||
|
||||
public @NonNull List<RecipientId> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
public int getSelectionLimit() {
|
||||
if (!selectionLimits.hasHardLimit()) {
|
||||
return ContactSelectionListFragment.NO_LIMIT;
|
||||
}
|
||||
|
||||
boolean containsSelf = members.indexOf(Recipient.self().getId()) != -1;
|
||||
|
||||
return selectionLimits.getHardLimit() - (containsSelf ? 1 : 0);
|
||||
}
|
||||
|
||||
public int getSelectionWarning() {
|
||||
if (!selectionLimits.hasRecommendedLimit()) {
|
||||
return ContactSelectionListFragment.NO_LIMIT;
|
||||
}
|
||||
|
||||
boolean containsSelf = members.indexOf(Recipient.self().getId()) != -1;
|
||||
|
||||
return selectionLimits.getRecommendedLimit() - (containsSelf ? 1 : 0);
|
||||
}
|
||||
|
||||
public int getRemainingCapacity() {
|
||||
return selectionLimits.getHardLimit() - members.size();
|
||||
}
|
||||
|
||||
public @NonNull List<RecipientId> getMembersWithoutSelf() {
|
||||
ArrayList<RecipientId> recipientIds = new ArrayList<>(members.size());
|
||||
RecipientId selfId = Recipient.self().getId();
|
||||
|
||||
for (RecipientId recipientId : members) {
|
||||
if (!recipientId.equals(selfId)) {
|
||||
recipientIds.add(recipientId);
|
||||
}
|
||||
}
|
||||
|
||||
return recipientIds;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,468 +0,0 @@
|
||||
package org.thoughtcrime.securesms.groups.ui.managegroup;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.BlockUnblockDialog;
|
||||
import org.thoughtcrime.securesms.ContactSelectionListFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.database.MentionUtil;
|
||||
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
|
||||
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.groups.GroupAccessControl;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.groups.ui.addmembers.AddMembersActivity;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupMentionSettingDialog;
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupLinkUrlAndStatus;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.util.livedata.Store;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ManageGroupViewModel extends ViewModel {
|
||||
|
||||
private static final int MAX_UNCOLLAPSED_MEMBERS = 6;
|
||||
private static final int SHOW_COLLAPSED_MEMBERS = 5;
|
||||
|
||||
private final Context context;
|
||||
private final ManageGroupRepository manageGroupRepository;
|
||||
private final LiveData<String> title;
|
||||
private final Store<Description> descriptionStore;
|
||||
private final LiveData<Description> description;
|
||||
private final LiveData<Boolean> isAdmin;
|
||||
private final LiveData<Boolean> canEditGroupAttributes;
|
||||
private final LiveData<Boolean> canAddMembers;
|
||||
private final LiveData<List<GroupMemberEntry.FullMember>> members;
|
||||
private final LiveData<Integer> pendingMemberCount;
|
||||
private final LiveData<Integer> pendingAndRequestingCount;
|
||||
private final LiveData<String> disappearingMessageTimer;
|
||||
private final LiveData<String> memberCountSummary;
|
||||
private final LiveData<String> fullMemberCountSummary;
|
||||
private final LiveData<GroupAccessControl> editMembershipRights;
|
||||
private final LiveData<GroupAccessControl> editGroupAttributesRights;
|
||||
private final LiveData<Recipient> groupRecipient;
|
||||
private final MutableLiveData<GroupViewState> groupViewState = new MutableLiveData<>(null);
|
||||
private final LiveData<MuteState> muteState;
|
||||
private final LiveData<Boolean> hasCustomNotifications;
|
||||
private final LiveData<Boolean> canCollapseMemberList;
|
||||
private final DefaultValueLiveData<CollapseState> memberListCollapseState = new DefaultValueLiveData<>(CollapseState.COLLAPSED);
|
||||
private final LiveData<Boolean> canLeaveGroup;
|
||||
private final LiveData<Boolean> canBlockGroup;
|
||||
private final LiveData<Boolean> canUnblockGroup;
|
||||
private final LiveData<Boolean> showLegacyIndicator;
|
||||
private final LiveData<String> mentionSetting;
|
||||
private final LiveData<Boolean> groupLinkOn;
|
||||
private final LiveData<GroupInfoMessage> groupInfoMessage;
|
||||
|
||||
private ManageGroupViewModel(@NonNull Context context, @NonNull GroupId groupId, @NonNull ManageGroupRepository manageGroupRepository) {
|
||||
this.context = context;
|
||||
this.manageGroupRepository = manageGroupRepository;
|
||||
|
||||
manageGroupRepository.getGroupState(groupId, this::groupStateLoaded);
|
||||
|
||||
LiveGroup liveGroup = new LiveGroup(groupId);
|
||||
|
||||
this.title = Transformations.map(liveGroup.getTitle(),
|
||||
title -> TextUtils.isEmpty(title) ? context.getString(R.string.Recipient_unknown)
|
||||
: title);
|
||||
this.groupRecipient = liveGroup.getGroupRecipient();
|
||||
this.isAdmin = liveGroup.isSelfAdmin();
|
||||
this.canCollapseMemberList = LiveDataUtil.combineLatest(memberListCollapseState,
|
||||
Transformations.map(liveGroup.getFullMembers(), m -> m.size() > MAX_UNCOLLAPSED_MEMBERS),
|
||||
(state, hasEnoughMembers) -> state != CollapseState.OPEN && hasEnoughMembers);
|
||||
this.members = LiveDataUtil.combineLatest(liveGroup.getFullMembers(),
|
||||
memberListCollapseState,
|
||||
ManageGroupViewModel::filterMemberList);
|
||||
this.pendingMemberCount = liveGroup.getPendingMemberCount();
|
||||
this.pendingAndRequestingCount = liveGroup.getPendingAndRequestingMemberCount();
|
||||
this.showLegacyIndicator = Transformations.map(groupRecipient, recipient -> recipient.requireGroupId().isV1());
|
||||
this.memberCountSummary = LiveDataUtil.combineLatest(liveGroup.getMembershipCountDescription(context.getResources()),
|
||||
this.showLegacyIndicator,
|
||||
(description, legacy) -> legacy ? String.format("%s · %s", description, context.getString(R.string.ManageGroupActivity_legacy_group))
|
||||
: description);
|
||||
this.fullMemberCountSummary = liveGroup.getFullMembershipCountDescription(context.getResources());
|
||||
this.editMembershipRights = liveGroup.getMembershipAdditionAccessControl();
|
||||
this.editGroupAttributesRights = liveGroup.getAttributesAccessControl();
|
||||
this.disappearingMessageTimer = Transformations.map(liveGroup.getExpireMessages(), expiration -> ExpirationUtil.getExpirationDisplayValue(context, expiration));
|
||||
this.canEditGroupAttributes = liveGroup.selfCanEditGroupAttributes();
|
||||
this.canAddMembers = liveGroup.selfCanAddMembers();
|
||||
this.muteState = Transformations.map(this.groupRecipient,
|
||||
recipient -> new MuteState(recipient.getMuteUntil(), recipient.isMuted()));
|
||||
this.hasCustomNotifications = LiveDataUtil.mapAsync(this.groupRecipient, manageGroupRepository::hasCustomNotifications);
|
||||
this.canLeaveGroup = liveGroup.isActive();
|
||||
this.canBlockGroup = Transformations.map(this.groupRecipient, recipient -> RecipientUtil.isBlockable(recipient) && !recipient.isBlocked());
|
||||
this.canUnblockGroup = Transformations.map(this.groupRecipient, Recipient::isBlocked);
|
||||
this.mentionSetting = Transformations.distinctUntilChanged(Transformations.map(this.groupRecipient,
|
||||
recipient -> MentionUtil.getMentionSettingDisplayValue(context, recipient.getMentionSetting())));
|
||||
this.groupLinkOn = Transformations.map(liveGroup.getGroupLink(), GroupLinkUrlAndStatus::isEnabled);
|
||||
this.groupInfoMessage = Transformations.map(this.groupRecipient,
|
||||
recipient -> {
|
||||
boolean showLegacyInfo = recipient.requireGroupId().isV1();
|
||||
|
||||
if (showLegacyInfo && recipient.getParticipants().size() > FeatureFlags.groupLimits().getHardLimit()) {
|
||||
return GroupInfoMessage.LEGACY_GROUP_TOO_LARGE;
|
||||
} else if (showLegacyInfo) {
|
||||
return GroupInfoMessage.LEGACY_GROUP_UPGRADE;
|
||||
} else if (groupId.isMms()) {
|
||||
return GroupInfoMessage.MMS_WARNING;
|
||||
} else {
|
||||
return GroupInfoMessage.NONE;
|
||||
}
|
||||
});
|
||||
|
||||
this.descriptionStore = new Store<>(Description.NONE);
|
||||
this.description = groupId.isV2() ? this.descriptionStore.getStateLiveData() : LiveDataUtil.empty();
|
||||
|
||||
if (groupId.isV2()) {
|
||||
this.descriptionStore.update(liveGroup.getDescription(), (description, state) -> new Description(description, state.shouldLinkifyWebLinks, state.canEditDescription));
|
||||
this.descriptionStore.update(LiveDataUtil.mapAsync(groupRecipient, r -> RecipientUtil.isMessageRequestAccepted(context, r)), (linkify, state) -> new Description(state.description, linkify, state.canEditDescription));
|
||||
this.descriptionStore.update(this.canEditGroupAttributes, (canEdit, state) -> new Description(state.description, state.shouldLinkifyWebLinks, canEdit));
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void groupStateLoaded(@NonNull ManageGroupRepository.GroupStateResult groupStateResult) {
|
||||
groupViewState.postValue(new GroupViewState(groupStateResult.getThreadId(),
|
||||
groupStateResult.getRecipient(),
|
||||
() -> new ThreadMediaLoader(context, groupStateResult.getThreadId(), MediaLoader.MediaType.GALLERY, MediaDatabase.Sorting.Newest).getCursor()));
|
||||
}
|
||||
|
||||
LiveData<List<GroupMemberEntry.FullMember>> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
LiveData<Integer> getPendingMemberCount() {
|
||||
return pendingMemberCount;
|
||||
}
|
||||
|
||||
LiveData<Integer> getPendingAndRequestingCount() {
|
||||
return pendingAndRequestingCount;
|
||||
}
|
||||
|
||||
LiveData<String> getMemberCountSummary() {
|
||||
return memberCountSummary;
|
||||
}
|
||||
|
||||
LiveData<String> getFullMemberCountSummary() {
|
||||
return fullMemberCountSummary;
|
||||
}
|
||||
|
||||
LiveData<Recipient> getGroupRecipient() {
|
||||
return groupRecipient;
|
||||
}
|
||||
|
||||
LiveData<GroupViewState> getGroupViewState() {
|
||||
return groupViewState;
|
||||
}
|
||||
|
||||
LiveData<String> getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
LiveData<Description> getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
LiveData<MuteState> getMuteState() {
|
||||
return muteState;
|
||||
}
|
||||
|
||||
LiveData<GroupAccessControl> getMembershipRights() {
|
||||
return editMembershipRights;
|
||||
}
|
||||
|
||||
LiveData<GroupAccessControl> getEditGroupAttributesRights() {
|
||||
return editGroupAttributesRights;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getIsAdmin() {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanEditGroupAttributes() {
|
||||
return canEditGroupAttributes;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanAddMembers() {
|
||||
return canAddMembers;
|
||||
}
|
||||
|
||||
LiveData<String> getDisappearingMessageTimer() {
|
||||
return disappearingMessageTimer;
|
||||
}
|
||||
|
||||
LiveData<Boolean> hasCustomNotifications() {
|
||||
return hasCustomNotifications;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanCollapseMemberList() {
|
||||
return canCollapseMemberList;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanBlockGroup() {
|
||||
return canBlockGroup;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanUnblockGroup() {
|
||||
return canUnblockGroup;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanLeaveGroup() {
|
||||
return canLeaveGroup;
|
||||
}
|
||||
|
||||
LiveData<String> getMentionSetting() {
|
||||
return mentionSetting;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getGroupLinkOn() {
|
||||
return groupLinkOn;
|
||||
}
|
||||
|
||||
LiveData<GroupInfoMessage> getGroupInfoMessage() {
|
||||
return groupInfoMessage;
|
||||
}
|
||||
|
||||
void applyMembershipRightsChange(@NonNull GroupAccessControl newRights) {
|
||||
manageGroupRepository.applyMembershipRightsChange(getGroupId(), newRights, this::showErrorToast);
|
||||
}
|
||||
|
||||
void applyAttributesRightsChange(@NonNull GroupAccessControl newRights) {
|
||||
manageGroupRepository.applyAttributesRightsChange(getGroupId(), newRights, this::showErrorToast);
|
||||
}
|
||||
|
||||
void blockAndLeave(@NonNull FragmentActivity activity) {
|
||||
manageGroupRepository.getRecipient(getGroupId(),
|
||||
recipient -> BlockUnblockDialog.showBlockFor(activity,
|
||||
activity.getLifecycle(),
|
||||
recipient,
|
||||
this::onBlockAndLeaveConfirmed));
|
||||
}
|
||||
|
||||
void unblock(@NonNull FragmentActivity activity) {
|
||||
manageGroupRepository.getRecipient(getGroupId(),
|
||||
recipient -> BlockUnblockDialog.showUnblockFor(activity, activity.getLifecycle(), recipient,
|
||||
() -> RecipientUtil.unblock(context, recipient)));
|
||||
}
|
||||
|
||||
void onAddMembers(@NonNull List<RecipientId> selected,
|
||||
@NonNull AsynchronousCallback.MainThread<AddMembersResult, GroupChangeFailureReason> callback)
|
||||
{
|
||||
manageGroupRepository.addMembers(getGroupId(), selected, callback.toWorkerCallback());
|
||||
}
|
||||
|
||||
void setMuteUntil(long muteUntil) {
|
||||
manageGroupRepository.setMuteUntil(getGroupId(), muteUntil);
|
||||
}
|
||||
|
||||
void clearMuteUntil() {
|
||||
manageGroupRepository.setMuteUntil(getGroupId(), 0);
|
||||
}
|
||||
|
||||
void revealCollapsedMembers() {
|
||||
memberListCollapseState.setValue(CollapseState.OPEN);
|
||||
}
|
||||
|
||||
void handleMentionNotificationSelection() {
|
||||
manageGroupRepository.getRecipient(getGroupId(), r -> GroupMentionSettingDialog.show(context, r.getMentionSetting(), setting -> manageGroupRepository.setMentionSetting(getGroupId(), setting)));
|
||||
}
|
||||
|
||||
private void onBlockAndLeaveConfirmed() {
|
||||
SimpleProgressDialog.DismissibleDialog dismissibleDialog = SimpleProgressDialog.showDelayed(context);
|
||||
|
||||
manageGroupRepository.blockAndLeaveGroup(getGroupId(),
|
||||
e -> {
|
||||
dismissibleDialog.dismiss();
|
||||
showErrorToast(e);
|
||||
},
|
||||
dismissibleDialog::dismiss);
|
||||
}
|
||||
|
||||
private @NonNull GroupId getGroupId() {
|
||||
return groupRecipient.getValue().requireGroupId();
|
||||
}
|
||||
|
||||
private static @NonNull List<GroupMemberEntry.FullMember> filterMemberList(@NonNull List<GroupMemberEntry.FullMember> members,
|
||||
@NonNull CollapseState collapseState)
|
||||
{
|
||||
if (collapseState == CollapseState.COLLAPSED && members.size() > MAX_UNCOLLAPSED_MEMBERS) {
|
||||
return members.subList(0, SHOW_COLLAPSED_MEMBERS);
|
||||
} else {
|
||||
return members;
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void showErrorToast(@NonNull GroupChangeFailureReason e) {
|
||||
ThreadUtil.runOnMain(() -> Toast.makeText(context, GroupErrors.getUserDisplayMessage(e), Toast.LENGTH_LONG).show());
|
||||
}
|
||||
|
||||
public void onAddMembersClick(@NonNull Fragment fragment, int resultCode) {
|
||||
manageGroupRepository.getGroupCapacity(getGroupId(), capacity -> {
|
||||
int remainingCapacity = capacity.getRemainingCapacity();
|
||||
if (remainingCapacity <= 0) {
|
||||
GroupLimitDialog.showHardLimitMessage(fragment.requireContext());
|
||||
} else {
|
||||
Intent intent = new Intent(fragment.requireActivity(), AddMembersActivity.class);
|
||||
intent.putExtra(AddMembersActivity.GROUP_ID, getGroupId().toString());
|
||||
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactsCursorLoader.DisplayMode.FLAG_PUSH);
|
||||
intent.putExtra(ContactSelectionListFragment.SELECTION_LIMITS, new SelectionLimits(capacity.getSelectionWarning(), capacity.getSelectionLimit()));
|
||||
intent.putParcelableArrayListExtra(ContactSelectionListFragment.CURRENT_SELECTION, new ArrayList<>(capacity.getMembersWithoutSelf()));
|
||||
fragment.startActivityForResult(intent, resultCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static final class AddMembersResult {
|
||||
private final int numberOfMembersAdded;
|
||||
private final List<Recipient> newInvitedMembers;
|
||||
|
||||
AddMembersResult(int numberOfMembersAdded, @NonNull List<Recipient> newInvitedMembers) {
|
||||
this.numberOfMembersAdded = numberOfMembersAdded;
|
||||
this.newInvitedMembers = newInvitedMembers;
|
||||
}
|
||||
|
||||
int getNumberOfMembersAdded() {
|
||||
return numberOfMembersAdded;
|
||||
}
|
||||
|
||||
List<Recipient> getNewInvitedMembers() {
|
||||
return newInvitedMembers;
|
||||
}
|
||||
}
|
||||
|
||||
static final class GroupViewState {
|
||||
private final long threadId;
|
||||
@NonNull private final Recipient groupRecipient;
|
||||
@NonNull private final CursorFactory mediaCursorFactory;
|
||||
|
||||
private GroupViewState(long threadId,
|
||||
@NonNull Recipient groupRecipient,
|
||||
@NonNull CursorFactory mediaCursorFactory)
|
||||
{
|
||||
this.threadId = threadId;
|
||||
this.groupRecipient = groupRecipient;
|
||||
this.mediaCursorFactory = mediaCursorFactory;
|
||||
}
|
||||
|
||||
long getThreadId() {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
@NonNull Recipient getGroupRecipient() {
|
||||
return groupRecipient;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
public long getMutedUntil() {
|
||||
return mutedUntil;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return isMuted;
|
||||
}
|
||||
}
|
||||
|
||||
enum GroupInfoMessage {
|
||||
NONE,
|
||||
LEGACY_GROUP_LEARN_MORE,
|
||||
LEGACY_GROUP_UPGRADE,
|
||||
LEGACY_GROUP_TOO_LARGE,
|
||||
MMS_WARNING
|
||||
}
|
||||
|
||||
private enum CollapseState {
|
||||
OPEN,
|
||||
COLLAPSED
|
||||
}
|
||||
|
||||
interface CursorFactory {
|
||||
Cursor create();
|
||||
}
|
||||
|
||||
public static class Description {
|
||||
private static final Description NONE = new Description("", false, false);
|
||||
|
||||
private final String description;
|
||||
private final boolean shouldLinkifyWebLinks;
|
||||
private final boolean canEditDescription;
|
||||
|
||||
public Description(String description, boolean shouldLinkifyWebLinks, boolean canEditDescription) {
|
||||
this.description = description;
|
||||
this.shouldLinkifyWebLinks = shouldLinkifyWebLinks;
|
||||
this.canEditDescription = canEditDescription;
|
||||
}
|
||||
|
||||
public @NonNull String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean shouldLinkifyWebLinks() {
|
||||
return shouldLinkifyWebLinks;
|
||||
}
|
||||
|
||||
public boolean canEditDescription() {
|
||||
return canEditDescription;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements ViewModelProvider.Factory {
|
||||
private final Context context;
|
||||
private final GroupId groupId;
|
||||
|
||||
public Factory(@NonNull Context context, @NonNull GroupId groupId) {
|
||||
this.context = context;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
//noinspection unchecked
|
||||
return (T) new ManageGroupViewModel(context, groupId, new ManageGroupRepository(context.getApplicationContext()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user