Allow setting local group names and avatars for MMS groups.
This commit is contained in:
parent
43e3ef2bee
commit
0bda1d46a2
14 changed files with 147 additions and 54 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue