Allow setting local group names and avatars for MMS groups.

fork-5.53.8
Alan Evans 2020-11-23 16:03:09 -04:00 zatwierdzone przez Alex Hart
rodzic 43e3ef2bee
commit 0bda1d46a2
14 zmienionych plików z 147 dodań i 54 usunięć

Wyświetl plik

@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.tracing.Trace;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
@ -38,7 +39,6 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.Closeable;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -254,7 +254,7 @@ public final class GroupDatabase extends Database {
.requireMms();
} else {
GroupId.Mms groupId = GroupId.createMms(new SecureRandom());
create(groupId, members);
create(groupId, null, members);
return groupId;
}
} finally {
@ -364,9 +364,10 @@ public final class GroupDatabase extends Database {
}
public void create(@NonNull GroupId.Mms groupId,
@Nullable String title,
@NonNull Collection<RecipientId> members)
{
create(groupId, null, members, null, null, null, null);
create(groupId, Util.isEmpty(title) ? null : title, members, null, null, null, null);
}
public GroupId.V2 create(@NonNull GroupMasterKey groupMasterKey,
@ -575,6 +576,18 @@ public final class GroupDatabase extends Database {
}
public void updateTitle(@NonNull GroupId.V1 groupId, String title) {
updateTitle((GroupId) groupId, title);
}
public void updateTitle(@NonNull GroupId.Mms groupId, @Nullable String title) {
updateTitle((GroupId) groupId, Util.isEmpty(title) ? null : title);
}
private void updateTitle(@NonNull GroupId groupId, String title) {
if (!groupId.isV1() && !groupId.isMms()) {
throw new AssertionError();
}
ContentValues contentValues = new ContentValues();
contentValues.put(TITLE, title);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
@ -587,7 +600,7 @@ public final class GroupDatabase extends Database {
/**
* Used to bust the Glide cache when an avatar changes.
*/
public void onAvatarUpdated(@NonNull GroupId.Push groupId, boolean hasAvatar) {
public void onAvatarUpdated(@NonNull GroupId groupId, boolean hasAvatar) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(AVATAR_ID, hasAvatar ? Math.abs(new SecureRandom().nextLong()) : 0);
@ -962,7 +975,7 @@ public final class GroupDatabase extends Database {
}
return GroupAccessControl.ONLY_ADMINS;
} else {
return id.isV1() ? GroupAccessControl.ALL_MEMBERS : GroupAccessControl.ONLY_ADMINS;
return GroupAccessControl.ALL_MEMBERS;
}
}

Wyświetl plik

@ -73,13 +73,15 @@ public final class GroupManager {
try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) {
return edit.updateGroupTitleAndAvatar(nameChanged ? name : null, avatar, avatarChanged);
}
} else {
} else if (groupId.isV1()) {
List<Recipient> members = DatabaseFactory.getGroupDatabase(context)
.getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
Set<RecipientId> recipientIds = getMemberIds(new HashSet<>(members));
return GroupManagerV1.updateGroup(context, groupId.requireV1(), recipientIds, avatar, name, 0);
} else {
return GroupManagerV1.updateGroup(context, groupId.requireMms(), avatar, name);
}
}

Wyświetl plik

@ -74,7 +74,15 @@ final class GroupManagerV1 {
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
return sendGroupUpdate(context, groupIdV1, memberIds, name, avatarBytes, memberIds.size() - 1);
} else {
groupDatabase.create(groupId.requireMms(), memberIds);
groupDatabase.create(groupId.requireMms(), name, memberIds);
try {
AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
} catch (IOException e) {
Log.w(TAG, "Failed to save avatar!", e);
}
groupDatabase.onAvatarUpdated(groupId, avatarBytes != null);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
return new GroupActionResult(groupRecipient, threadId, memberIds.size() - 1, Collections.emptyList());
}
@ -112,6 +120,28 @@ final class GroupManagerV1 {
}
}
static GroupActionResult updateGroup(@NonNull Context context,
@NonNull GroupId.Mms groupId,
@Nullable byte[] avatarBytes,
@Nullable String name)
{
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
groupDatabase.updateTitle(groupId, name);
groupDatabase.onAvatarUpdated(groupId, avatarBytes != null);
try {
AvatarHelper.setAvatar(context, groupRecipientId, avatarBytes != null ? new ByteArrayInputStream(avatarBytes) : null);
} catch (IOException e) {
Log.w(TAG, "Failed to save avatar!", e);
}
return new GroupActionResult(groupRecipient, threadId, 0, Collections.emptyList());
}
private static GroupActionResult sendGroupUpdate(@NonNull Context context,
@NonNull GroupId.V1 groupId,
@NonNull Set<RecipientId> members,

Wyświetl plik

@ -116,8 +116,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
viewModel.getCanSubmitForm().observe(getViewLifecycleOwner(), isFormValid -> setCreateEnabled(isFormValid, true));
viewModel.getIsMms().observe(getViewLifecycleOwner(), isMms -> {
mmsWarning.setVisibility(isMms ? View.VISIBLE : View.GONE);
name.setVisibility(isMms ? View.GONE : View.VISIBLE);
avatar.setVisibility(isMms ? View.GONE : View.VISIBLE);
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);
});
viewModel.getNonGv2CapableMembers().observe(getViewLifecycleOwner(), nonGv2CapableMembers -> {

Wyświetl plik

@ -48,11 +48,11 @@ final class AddGroupDetailsRepository {
});
}
void createPushGroup(@NonNull Set<RecipientId> members,
@Nullable byte[] avatar,
@Nullable String name,
boolean mms,
Consumer<GroupCreateResult> resultConsumer)
void createGroup(@NonNull Set<RecipientId> members,
@Nullable byte[] avatar,
@Nullable String name,
boolean mms,
Consumer<GroupCreateResult> resultConsumer)
{
SignalExecutors.BOUNDED.execute(() -> {
Set<Recipient> recipients = new HashSet<>(Stream.of(members).map(Recipient::resolved).toList());

Wyświetl plik

@ -117,7 +117,7 @@ public final class AddGroupDetailsViewModel extends ViewModel {
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 = isGroupMms ? "" : name.getValue();
String groupName = name.getValue();
if (!isGroupMms && TextUtils.isEmpty(groupName)) {
groupCreateResult.postValue(GroupCreateResult.error(GroupCreateResult.Error.Type.ERROR_INVALID_NAME));
@ -129,11 +129,11 @@ public final class AddGroupDetailsViewModel extends ViewModel {
return;
}
repository.createPushGroup(memberIds,
avatarBytes,
groupName,
isGroupMms,
groupCreateResult::postValue);
repository.createGroup(memberIds,
avatarBytes,
groupName,
isGroupMms,
groupCreateResult::postValue);
}
private static @NonNull List<GroupMemberEntry.NewGroupCandidate> filterDeletedMembers(@NonNull List<GroupMemberEntry.NewGroupCandidate> members, @NonNull Set<RecipientId> deleted) {

Wyświetl plik

@ -416,7 +416,7 @@ public class ManageGroupFragment extends LoggingFragment {
public boolean onMenuItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_edit) {
startActivity(EditProfileActivity.getIntentForGroupProfile(requireActivity(), getGroupId().requirePush()));
startActivity(EditProfileActivity.getIntentForGroupProfile(requireActivity(), getGroupId()));
return true;
}

Wyświetl plik

@ -8,6 +8,7 @@ import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
@ -22,14 +23,14 @@ import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
class EditPushGroupProfileRepository implements EditProfileRepository {
class EditGroupProfileRepository implements EditProfileRepository {
private static final String TAG = Log.tag(EditPushGroupProfileRepository.class);
private static final String TAG = Log.tag(EditGroupProfileRepository.class);
private final Context context;
private final GroupId.Push groupId;
private final Context context;
private final GroupId groupId;
EditPushGroupProfileRepository(@NonNull Context context, @NonNull GroupId.Push groupId) {
EditGroupProfileRepository(@NonNull Context context, @NonNull GroupId groupId) {
this.context = context.getApplicationContext();
this.groupId = groupId;
}
@ -64,7 +65,18 @@ class EditPushGroupProfileRepository implements EditProfileRepository {
@Override
public void getCurrentName(@NonNull Consumer<String> nameConsumer) {
SimpleTask.run(() -> Recipient.resolved(getRecipientId()).getName(context), nameConsumer::accept);
SimpleTask.run(() -> {
RecipientId recipientId = getRecipientId();
Recipient recipient = Recipient.resolved(recipientId);
return DatabaseFactory.getGroupDatabase(context)
.getGroup(recipientId)
.transform(groupRecord -> {
String title = groupRecord.getTitle();
return title == null ? "" : title;
})
.or(() -> recipient.getName(context));
}, nameConsumer::accept);
}
@Override

Wyświetl plik

@ -44,7 +44,7 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
return intent;
}
public static @NonNull Intent getIntentForGroupProfile(@NonNull Context context, @NonNull GroupId.Push groupId) {
public static @NonNull Intent getIntentForGroupProfile(@NonNull Context context, @NonNull GroupId groupId) {
Intent intent = new Intent(context, EditProfileActivity.class);
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, true);
intent.putExtra(EditProfileActivity.GROUP_ID, groupId.toString());

Wyświetl plik

@ -119,11 +119,10 @@ public class EditProfileFragment extends LoggingFragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
final GroupId groupId = GroupId.parseNullableOrThrow(requireArguments().getString(GROUP_ID, null));
final GroupId.Push pushGroupId = groupId != null ? groupId.requirePush() : null;
GroupId groupId = GroupId.parseNullableOrThrow(requireArguments().getString(GROUP_ID, null));
initializeResources(view, pushGroupId != null);
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), pushGroupId, savedInstanceState != null);
initializeResources(view, groupId);
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), groupId, savedInstanceState != null);
initializeProfileAvatar();
initializeProfileName();
initializeUsername();
@ -174,11 +173,11 @@ public class EditProfileFragment extends LoggingFragment {
}
}
private void initializeViewModel(boolean excludeSystem, @Nullable GroupId.Push groupId, boolean hasSavedInstanceState) {
private void initializeViewModel(boolean excludeSystem, @Nullable GroupId groupId, boolean hasSavedInstanceState) {
EditProfileRepository repository;
if (groupId != null) {
repository = new EditPushGroupProfileRepository(requireContext(), groupId);
repository = new EditGroupProfileRepository(requireContext(), groupId);
} else {
repository = new EditSelfProfileRepository(requireContext(), excludeSystem);
}
@ -189,8 +188,9 @@ public class EditProfileFragment extends LoggingFragment {
.get(EditProfileViewModel.class);
}
private void initializeResources(@NonNull View view, boolean isEditingGroup) {
Bundle arguments = requireArguments();
private void initializeResources(@NonNull View view, @Nullable GroupId groupId) {
Bundle arguments = requireArguments();
boolean isEditingGroup = groupId != null;
this.toolbar = view.findViewById(R.id.toolbar);
this.title = view.findViewById(R.id.title);
@ -213,10 +213,13 @@ public class EditProfileFragment extends LoggingFragment {
this.avatar.setOnClickListener(v -> startAvatarSelection());
this.givenName .addTextChangedListener(new AfterTextChanged(s -> {
trimInPlace(s, isEditingGroup);
viewModel.setGivenName(s.toString());
}));
this.givenName.addTextChangedListener(new AfterTextChanged(s -> {
trimInPlace(s, isEditingGroup);
viewModel.setGivenName(s.toString());
}));
view.findViewById(R.id.mms_group_hint)
.setVisibility(isEditingGroup && groupId.isMms() ? View.VISIBLE : View.GONE);
if (isEditingGroup) {
givenName.setHint(R.string.EditProfileFragment__group_name);

Wyświetl plik

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.profiles.edit;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
@ -30,13 +29,15 @@ class EditProfileViewModel extends ViewModel {
private final MutableLiveData<byte[]> originalAvatar = new MutableLiveData<>();
private final MutableLiveData<Optional<String>> internalUsername = new MutableLiveData<>();
private final MutableLiveData<String> originalDisplayName = new MutableLiveData<>();
private final LiveData<Boolean> isFormValid = Transformations.map(trimmedGivenName, s -> s.length() > 0);
private final LiveData<Boolean> isFormValid;
private final EditProfileRepository repository;
private final GroupId groupId;
private EditProfileViewModel(@NonNull EditProfileRepository repository, boolean hasInstanceState, @Nullable GroupId groupId) {
this.repository = repository;
this.groupId = groupId;
this.repository = repository;
this.groupId = groupId;
this.isFormValid = groupId != null && groupId.isMms() ? LiveDataUtil.just(true)
: Transformations.map(trimmedGivenName, s -> s.length() > 0);
if (!hasInstanceState) {
if (groupId != null) {

Wyświetl plik

@ -40,23 +40,41 @@
app:layout_constraintStart_toEndOf="@id/group_avatar"
app:layout_constraintTop_toTopOf="@id/group_avatar" />
<TextView
<LinearLayout
android:id="@+id/mms_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="?colorAccent"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="@string/AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/white"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/gv2_warning"
app:layout_constraintTop_toBottomOf="@id/group_avatar"
tools:visibility="visible" />
tools:visibility="visible">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorAccent"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:text="@string/AddGroupDetailsFragment_custom_mms_group_names_and_photos_will_only_be_visible_to_you"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorAccent"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="@string/AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/white" />
</LinearLayout>
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/gv2_warning"

Wyświetl plik

@ -137,6 +137,18 @@
android:inputType="textPersonName"
android:singleLine="true" />
<TextView
android:id="@+id/mms_group_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:text="@string/CreateProfileActivity_custom_mms_group_names_and_photos_will_only_be_visible_to_you"
android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/signal_text_secondary"
android:visibility="gone"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/family_name"
style="@style/Signal.Text.Body"

Wyświetl plik

@ -690,11 +690,13 @@
<string name="AddGroupDetailsFragment__create">Create</string>
<string name="AddGroupDetailsFragment__members">Members</string>
<string name="AddGroupDetailsFragment__group_name_required">Group name (required)</string>
<string name="AddGroupDetailsFragment__group_name_optional">Group name (optional)</string>
<string name="AddGroupDetailsFragment__this_field_is_required">This field is required.</string>
<string name="AddGroupDetailsFragment__groups_require_at_least_two_members">Groups require at least two members.</string>
<string name="AddGroupDetailsFragment__group_creation_failed">Group creation failed.</string>
<string name="AddGroupDetailsFragment__try_again_later">Try again later.</string>
<string name="AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt">You\'ve selected a contact that doesn\'t support Signal groups, so this group will be MMS.</string>
<string name="AddGroupDetailsFragment_custom_mms_group_names_and_photos_will_only_be_visible_to_you">Custom MMS group names and photos will only be visible to you.</string>
<string name="AddGroupDetailsFragment__remove">Remove</string>
<string name="AddGroupDetailsFragment__sms_contact">SMS contact</string>
<string name="AddGroupDetailsFragment__remove_s_from_this_group">Remove %1$s from this group?</string>
@ -2073,6 +2075,7 @@
<string name="CreateProfileActivity_next">Next</string>
<string name="CreateProfileActivity__username">Username</string>
<string name="CreateProfileActivity__create_a_username">Create a username</string>
<string name="CreateProfileActivity_custom_mms_group_names_and_photos_will_only_be_visible_to_you">Custom MMS group names and photos will only be visible to you.</string>
<!-- EditProfileFragment -->
<string name="EditProfileFragment__edit_group_name_and_photo">Edit group name and photo</string>