mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 09:20:19 +01:00
Add universal disappearing messages.
This commit is contained in:
committed by
Greyson Parrelli
parent
8c6a88374b
commit
defd5e8047
@@ -36,11 +36,12 @@ public final class GroupManager {
|
||||
private static final String TAG = Log.tag(GroupManager.class);
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull GroupActionResult createGroup(@NonNull Context context,
|
||||
@NonNull Set<Recipient> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name,
|
||||
boolean mms)
|
||||
public static @NonNull GroupActionResult createGroup(@NonNull Context context,
|
||||
@NonNull Set<Recipient> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name,
|
||||
boolean mms,
|
||||
int disappearingMessagesTimer)
|
||||
throws GroupChangeBusyException, GroupChangeFailedException, IOException
|
||||
{
|
||||
boolean shouldAttemptToCreateV2 = !mms && !SignalStore.internalValues().gv2DoNotCreateGv2Groups();
|
||||
@@ -49,7 +50,7 @@ public final class GroupManager {
|
||||
if (shouldAttemptToCreateV2) {
|
||||
try {
|
||||
try (GroupManagerV2.GroupCreator groupCreator = new GroupManagerV2(context).create()) {
|
||||
return groupCreator.createGroup(memberIds, name, avatar);
|
||||
return groupCreator.createGroup(memberIds, name, avatar, disappearingMessagesTimer);
|
||||
}
|
||||
} catch (MembershipNotSuitableForV2Exception e) {
|
||||
Log.w(TAG, "Attempted to make a GV2, but membership was not suitable, falling back to GV1", e);
|
||||
|
||||
@@ -244,23 +244,15 @@ final class GroupManagerV2 {
|
||||
@WorkerThread
|
||||
@NonNull GroupManager.GroupActionResult createGroup(@NonNull Collection<RecipientId> members,
|
||||
@Nullable String name,
|
||||
@Nullable byte[] avatar)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
|
||||
{
|
||||
return createGroup(name, avatar, members);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull GroupManager.GroupActionResult createGroup(@Nullable String name,
|
||||
@Nullable byte[] avatar,
|
||||
@NonNull Collection<RecipientId> members)
|
||||
@Nullable byte[] avatar,
|
||||
int disappearingMessagesTimer)
|
||||
throws GroupChangeFailedException, IOException, MembershipNotSuitableForV2Exception
|
||||
{
|
||||
GroupSecretParams groupSecretParams = GroupSecretParams.generate();
|
||||
DecryptedGroup decryptedGroup;
|
||||
|
||||
try {
|
||||
decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, Member.Role.DEFAULT, 0);
|
||||
decryptedGroup = createGroupOnServer(groupSecretParams, name, avatar, members, Member.Role.DEFAULT, disappearingMessagesTimer);
|
||||
} catch (GroupAlreadyExistsException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public enum GroupChangeFailureReason {
|
||||
NETWORK,
|
||||
OTHER;
|
||||
|
||||
public static @NonNull GroupChangeFailureReason fromException(@NonNull Exception e) {
|
||||
public static @NonNull GroupChangeFailureReason fromException(@NonNull Throwable e) {
|
||||
if (e instanceof MembershipNotSuitableForV2Exception) return GroupChangeFailureReason.NOT_CAPABLE;
|
||||
if (e instanceof IOException) return GroupChangeFailureReason.NETWORK;
|
||||
if (e instanceof GroupNotAMemberException) return GroupChangeFailureReason.NOT_A_MEMBER;
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -30,8 +31,10 @@ import com.dd.CircularProgressButton;
|
||||
import org.signal.core.util.EditTextUtil;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.settings.app.privacy.expire.ExpireTimerSettingsFragment;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.groups.ui.creategroup.dialogs.NonGv2MemberDialog;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
@@ -40,7 +43,9 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
@@ -54,6 +59,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
|
||||
private static final int AVATAR_PLACEHOLDER_INSET_DP = 18;
|
||||
private static final short REQUEST_CODE_AVATAR = 27621;
|
||||
private static final short REQUEST_DISAPPEARING_TIMER = 28621;
|
||||
|
||||
private CircularProgressButton create;
|
||||
private Callback callback;
|
||||
@@ -61,6 +67,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
private Drawable avatarPlaceholder;
|
||||
private EditText name;
|
||||
private Toolbar toolbar;
|
||||
private View disappearingMessagesRow;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
@@ -83,17 +90,19 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
create = view.findViewById(R.id.create);
|
||||
name = view.findViewById(R.id.name);
|
||||
toolbar = view.findViewById(R.id.toolbar);
|
||||
create = view.findViewById(R.id.create);
|
||||
name = view.findViewById(R.id.name);
|
||||
toolbar = view.findViewById(R.id.toolbar);
|
||||
disappearingMessagesRow = view.findViewById(R.id.group_disappearing_messages_row);
|
||||
|
||||
setCreateEnabled(false, false);
|
||||
|
||||
GroupMemberListView members = view.findViewById(R.id.member_list);
|
||||
ImageView avatar = view.findViewById(R.id.group_avatar);
|
||||
View mmsWarning = view.findViewById(R.id.mms_warning);
|
||||
LearnMoreTextView gv2Warning = view.findViewById(R.id.gv2_warning);
|
||||
View addLater = view.findViewById(R.id.add_later);
|
||||
GroupMemberListView members = view.findViewById(R.id.member_list);
|
||||
ImageView avatar = view.findViewById(R.id.group_avatar);
|
||||
View mmsWarning = view.findViewById(R.id.mms_warning);
|
||||
LearnMoreTextView gv2Warning = view.findViewById(R.id.gv2_warning);
|
||||
View addLater = view.findViewById(R.id.add_later);
|
||||
TextView disappearingMessageValue = view.findViewById(R.id.group_disappearing_messages_value);
|
||||
|
||||
avatarPlaceholder = VectorDrawableCompat.create(getResources(), R.drawable.ic_camera_outline_32_ultramarine, requireActivity().getTheme());
|
||||
|
||||
@@ -115,6 +124,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
});
|
||||
viewModel.getCanSubmitForm().observe(getViewLifecycleOwner(), isFormValid -> setCreateEnabled(isFormValid, true));
|
||||
viewModel.getIsMms().observe(getViewLifecycleOwner(), isMms -> {
|
||||
disappearingMessagesRow.setVisibility(isMms ? View.GONE : View.VISIBLE);
|
||||
mmsWarning.setVisibility(isMms ? View.VISIBLE : View.GONE);
|
||||
name.setHint(isMms ? R.string.AddGroupDetailsFragment__group_name_optional : R.string.AddGroupDetailsFragment__group_name_required);
|
||||
toolbar.setTitle(isMms ? R.string.AddGroupDetailsFragment__create_group : R.string.AddGroupDetailsFragment__name_this_group);
|
||||
@@ -143,6 +153,11 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getDisappearingMessagesTimer().observe(getViewLifecycleOwner(), timer -> disappearingMessageValue.setText(ExpirationUtil.getExpirationDisplayValue(requireContext(), timer)));
|
||||
disappearingMessagesRow.setOnClickListener(v -> {
|
||||
startActivityForResult(RecipientDisappearingMessagesActivity.forCreateGroup(requireContext(), viewModel.getDisappearingMessagesTimer().getValue()), REQUEST_DISAPPEARING_TIMER);
|
||||
});
|
||||
|
||||
name.requestFocus();
|
||||
}
|
||||
|
||||
@@ -175,6 +190,8 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
}
|
||||
});
|
||||
} else if (requestCode == REQUEST_DISAPPEARING_TIMER && resultCode == Activity.RESULT_OK && data != null) {
|
||||
viewModel.setDisappearingMessageTimer(data.getIntExtra(ExpireTimerSettingsFragment.FOR_RESULT_VALUE, SignalStore.settings().getUniversalExpireTimer()));
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.groups.GroupChangeException;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV2CapabilityChecker;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
@@ -48,17 +49,24 @@ final class AddGroupDetailsRepository {
|
||||
});
|
||||
}
|
||||
|
||||
void createGroup(@NonNull Set<RecipientId> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name,
|
||||
boolean mms,
|
||||
void createGroup(@NonNull Set<RecipientId> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name,
|
||||
boolean mms,
|
||||
@Nullable Integer disappearingMessagesTimer,
|
||||
Consumer<GroupCreateResult> resultConsumer)
|
||||
{
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
Set<Recipient> recipients = new HashSet<>(Stream.of(members).map(Recipient::resolved).toList());
|
||||
|
||||
try {
|
||||
GroupManager.GroupActionResult result = GroupManager.createGroup(context, recipients, avatar, name, mms);
|
||||
GroupManager.GroupActionResult result = GroupManager.createGroup(context,
|
||||
recipients,
|
||||
avatar,
|
||||
name,
|
||||
mms,
|
||||
disappearingMessagesTimer != null ? disappearingMessagesTimer
|
||||
: SignalStore.settings().getUniversalExpireTimer());
|
||||
|
||||
resultConsumer.accept(GroupCreateResult.success(result));
|
||||
} catch (GroupChangeBusyException e) {
|
||||
|
||||
@@ -32,10 +32,11 @@ import java.util.Set;
|
||||
public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
|
||||
private final LiveData<List<GroupMemberEntry.NewGroupCandidate>> members;
|
||||
private final DefaultValueLiveData<Set<RecipientId>> deleted = new DefaultValueLiveData<>(new HashSet<>());
|
||||
private final MutableLiveData<String> name = new MutableLiveData<>("");
|
||||
private final MutableLiveData<byte[]> avatar = new MutableLiveData<>();
|
||||
private final SingleLiveEvent<GroupCreateResult> groupCreateResult = new SingleLiveEvent<>();
|
||||
private final DefaultValueLiveData<Set<RecipientId>> deleted = new DefaultValueLiveData<>(new HashSet<>());
|
||||
private final MutableLiveData<String> name = new MutableLiveData<>("");
|
||||
private final MutableLiveData<byte[]> avatar = new MutableLiveData<>();
|
||||
private final SingleLiveEvent<GroupCreateResult> groupCreateResult = new SingleLiveEvent<>();
|
||||
private final MutableLiveData<Integer> disappearingMessagesTimer = new MutableLiveData<>(SignalStore.settings().getUniversalExpireTimer());
|
||||
private final LiveData<Boolean> isMms;
|
||||
private final LiveData<Boolean> canSubmitForm;
|
||||
private final AddGroupDetailsRepository repository;
|
||||
@@ -47,12 +48,10 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
this.repository = repository;
|
||||
|
||||
MutableLiveData<List<GroupMemberEntry.NewGroupCandidate>> initialMembers = new MutableLiveData<>();
|
||||
LiveData<Boolean> isValidName = Transformations.map(name, name -> !TextUtils.isEmpty(name));
|
||||
|
||||
LiveData<Boolean> isValidName = Transformations.map(name, name -> !TextUtils.isEmpty(name));
|
||||
|
||||
members = LiveDataUtil.combineLatest(initialMembers, deleted, AddGroupDetailsViewModel::filterDeletedMembers);
|
||||
|
||||
isMms = Transformations.map(members, AddGroupDetailsViewModel::isAnyForcedSms);
|
||||
members = LiveDataUtil.combineLatest(initialMembers, deleted, AddGroupDetailsViewModel::filterDeletedMembers);
|
||||
isMms = Transformations.map(members, AddGroupDetailsViewModel::isAnyForcedSms);
|
||||
|
||||
LiveData<List<GroupMemberEntry.NewGroupCandidate>> membersToCheckGv2CapabilityOf = LiveDataUtil.combineLatest(isMms, members, (forcedMms, memberList) -> {
|
||||
if (SignalStore.internalValues().gv2DoNotCreateGv2Groups() || forcedMms) {
|
||||
@@ -94,6 +93,10 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
return nonGv2CapableMembers;
|
||||
}
|
||||
|
||||
@NonNull LiveData<Integer> getDisappearingMessagesTimer() {
|
||||
return disappearingMessagesTimer;
|
||||
}
|
||||
|
||||
void setAvatar(@Nullable byte[] avatar) {
|
||||
this.avatar.setValue(avatar);
|
||||
}
|
||||
@@ -107,18 +110,19 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
void delete(@NonNull RecipientId recipientId) {
|
||||
Set<RecipientId> deleted = this.deleted.getValue();
|
||||
Set<RecipientId> deleted = this.deleted.getValue();
|
||||
|
||||
deleted.add(recipientId);
|
||||
this.deleted.setValue(deleted);
|
||||
}
|
||||
|
||||
void create() {
|
||||
List<GroupMemberEntry.NewGroupCandidate> members = Objects.requireNonNull(this.members.getValue());
|
||||
Set<RecipientId> memberIds = Stream.of(members).map(member -> member.getMember().getId()).collect(Collectors.toSet());
|
||||
byte[] avatarBytes = avatar.getValue();
|
||||
boolean isGroupMms = isMms.getValue() == Boolean.TRUE;
|
||||
String groupName = name.getValue();
|
||||
List<GroupMemberEntry.NewGroupCandidate> members = Objects.requireNonNull(this.members.getValue());
|
||||
Set<RecipientId> memberIds = Stream.of(members).map(member -> member.getMember().getId()).collect(Collectors.toSet());
|
||||
byte[] avatarBytes = avatar.getValue();
|
||||
boolean isGroupMms = isMms.getValue() == Boolean.TRUE;
|
||||
String groupName = name.getValue();
|
||||
Integer disappearingTimer = disappearingMessagesTimer.getValue();
|
||||
|
||||
if (!isGroupMms && TextUtils.isEmpty(groupName)) {
|
||||
groupCreateResult.postValue(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_INVALID_NAME));
|
||||
@@ -129,6 +133,7 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
avatarBytes,
|
||||
groupName,
|
||||
isGroupMms,
|
||||
disappearingTimer,
|
||||
groupCreateResult::postValue);
|
||||
}
|
||||
|
||||
@@ -143,6 +148,10 @@ public final class AddGroupDetailsViewModel extends ViewModel {
|
||||
.anyMatch(member -> !member.getMember().isRegistered());
|
||||
}
|
||||
|
||||
public void setDisappearingMessageTimer(int timer) {
|
||||
disappearingMessagesTimer.setValue(timer);
|
||||
}
|
||||
|
||||
static final class Factory implements ViewModelProvider.Factory {
|
||||
|
||||
private final Collection<RecipientId> recipientIds;
|
||||
|
||||
@@ -58,6 +58,7 @@ 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;
|
||||
@@ -280,7 +281,12 @@ public class ManageGroupFragment extends LoggingFragment {
|
||||
|
||||
viewModel.getDisappearingMessageTimer().observe(getViewLifecycleOwner(), string -> disappearingMessages.setText(string));
|
||||
|
||||
disappearingMessagesRow.setOnClickListener(v -> viewModel.handleExpirationSelection());
|
||||
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()));
|
||||
|
||||
|
||||
@@ -79,17 +79,6 @@ final class ManageGroupRepository {
|
||||
return new GroupStateResult(threadId, groupRecipient);
|
||||
}
|
||||
|
||||
void setExpiration(@NonNull GroupId groupId, int newExpirationTime, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.updateGroupTimer(context, groupId.requirePush(), newExpirationTime);
|
||||
} catch (GroupChangeException | IOException e) {
|
||||
Log.w(TAG, e);
|
||||
error.onError(GroupChangeFailureReason.fromException(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void applyMembershipRightsChange(@NonNull GroupId groupId, @NonNull GroupAccessControl newRights, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
|
||||
@@ -257,14 +257,6 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
return groupInfoMessage;
|
||||
}
|
||||
|
||||
void handleExpirationSelection() {
|
||||
manageGroupRepository.getRecipient(getGroupId(),
|
||||
groupRecipient ->
|
||||
ExpirationDialog.show(context,
|
||||
groupRecipient.getExpireMessages(),
|
||||
expirationTime -> manageGroupRepository.setExpiration(getGroupId(), expirationTime, this::showErrorToast)));
|
||||
}
|
||||
|
||||
void applyMembershipRightsChange(@NonNull GroupAccessControl newRights) {
|
||||
manageGroupRepository.applyMembershipRightsChange(getGroupId(), newRights, this::showErrorToast);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user