diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f4b586b994..b5d5ce3e91 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -421,9 +421,8 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
+ android:theme="@style/TextSecure.LightRegistrationTheme"
+ android:windowSoftInputMode="adjustResize" />
{
- if (recipientClickListener != null) {
+ if (recipient.equals(Recipient.self())) {
+ this.itemView.setEnabled(false);
+ return;
+ }
+
+ this.itemView.setEnabled(true);
+ this.itemView.setOnClickListener(v -> {
+ if (recipientClickListener != null && getAdapterPosition() != RecyclerView.NO_POSITION) {
recipientClickListener.onClick(recipient);
}
- };
- this.avatar.setOnClickListener(onClickListener);
- this.recipient.setOnClickListener(onClickListener);
+ });
}
void bind(@NonNull GroupMemberEntry memberEntry) {
@@ -151,8 +156,7 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter {
busyProgress.setVisibility(busy ? View.VISIBLE : View.GONE);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListView.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListView.java
index cd2f62c2c3..0dd2ae2f7d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/GroupMemberListView.java
@@ -34,7 +34,10 @@ public final class GroupMemberListView extends RecyclerView {
}
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
- setHasFixedSize(true);
+ if (maxHeight > 0) {
+ setHasFixedSize(true);
+ }
+
setLayoutManager(new LinearLayoutManager(context));
setAdapter(membersAdapter);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java
index 6cae0a0dc2..c60a5f2211 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupFragment.java
@@ -10,14 +10,12 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.constraintlayout.widget.Group;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -31,6 +29,8 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
+import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
+import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog;
@@ -42,6 +42,7 @@ 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.util.DateUtils;
@@ -71,16 +72,28 @@ public class ManageGroupFragment extends Fragment {
private View pendingMembersCard;
private ManageGroupViewModel.CursorFactory cursorFactory;
private View photoRailLabel;
- private Button editGroupAccessValue;
- private Button editGroupMembershipValue;
- private Button disappearingMessages;
- private Button blockGroup;
- private Button leaveGroup;
- private Button addMembers;
+ private View editGroupAccessRow;
+ private TextView editGroupAccessValue;
+ private View editGroupMembershipRow;
+ private TextView editGroupMembershipValue;
+ private View disappearingMessagesRow;
+ private TextView disappearingMessages;
+ private TextView blockGroup;
+ private TextView leaveGroup;
+ private TextView addMembers;
private Switch muteNotificationsSwitch;
+ private View muteNotificationsRow;
private TextView muteNotificationsUntilLabel;
private TextView customNotificationsButton;
- private Group customNotificationsControls;
+ private View customNotificationsRow;
+ private View toggleAllMembers;
+
+ private final Recipient.FallbackPhotoProvider fallbackPhotoProvider = new Recipient.FallbackPhotoProvider() {
+ @Override
+ public @NonNull FallbackContactPhoto getPhotoForGroup() {
+ return new ResourceContactPhoto(R.drawable.ic_group_80);
+ }
+ };
static ManageGroupFragment newInstance(@NonNull String groupId) {
ManageGroupFragment fragment = new ManageGroupFragment();
@@ -116,16 +129,21 @@ public class ManageGroupFragment extends Fragment {
accessControlCard = view.findViewById(R.id.group_access_control_card);
pendingMembersCard = view.findViewById(R.id.group_pending_card);
photoRailLabel = view.findViewById(R.id.rail_label);
+ 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);
+ disappearingMessagesRow = view.findViewById(R.id.disappearing_messages_row);
disappearingMessages = view.findViewById(R.id.disappearing_messages);
blockGroup = view.findViewById(R.id.blockGroup);
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);
- customNotificationsControls = view.findViewById(R.id.group_custom_notifications_controls);
+ customNotificationsRow = view.findViewById(R.id.group_custom_notifications_row);
+ toggleAllMembers = view.findViewById(R.id.toggle_all_members);
return view;
}
@@ -142,6 +160,15 @@ public class ManageGroupFragment extends Fragment {
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.getPendingMemberCount().observe(getViewLifecycleOwner(),
members -> {
if (members > 0) {
@@ -156,6 +183,8 @@ public class ManageGroupFragment extends Fragment {
}
});
+ avatar.setFallbackPhotoProvider(fallbackPhotoProvider);
+
viewModel.getTitle().observe(getViewLifecycleOwner(), groupTitle::setText);
viewModel.getMemberCountSummary().observe(getViewLifecycleOwner(), memberCountUnderAvatar::setText);
viewModel.getFullMemberCountSummary().observe(getViewLifecycleOwner(), memberCountAboveList::setText);
@@ -173,7 +202,6 @@ public class ManageGroupFragment extends Fragment {
ViewCompat.getLayoutDirection(threadPhotoRailView) == ViewCompat.LAYOUT_DIRECTION_LTR),
RETURN_FROM_MEDIA));
- accessControlCard.setVisibility(vs.getGroupRecipient().requireGroupId().isV2() ? View.VISIBLE : View.GONE);
pendingMembersCard.setVisibility(vs.getGroupRecipient().requireGroupId().isV2() ? View.VISIBLE : View.GONE);
});
@@ -185,7 +213,7 @@ public class ManageGroupFragment extends Fragment {
viewModel.getDisappearingMessageTimer().observe(getViewLifecycleOwner(), string -> disappearingMessages.setText(string));
- disappearingMessages.setOnClickListener(v -> viewModel.handleExpirationSelection());
+ disappearingMessagesRow.setOnClickListener(v -> viewModel.handleExpirationSelection());
blockGroup.setOnClickListener(v -> viewModel.blockAndLeave(requireActivity()));
addMembers.setOnClickListener(v -> {
@@ -197,7 +225,7 @@ public class ManageGroupFragment extends Fragment {
viewModel.getMembershipRights().observe(getViewLifecycleOwner(), r -> {
if (r != null) {
editGroupMembershipValue.setText(r.getString());
- editGroupMembershipValue.setOnClickListener(v -> new GroupRightsDialog(context, GroupRightsDialog.Type.MEMBERSHIP, r, (from, to) -> viewModel.applyMembershipRightsChange(to)).show());
+ editGroupMembershipRow.setOnClickListener(v -> new GroupRightsDialog(context, GroupRightsDialog.Type.MEMBERSHIP, r, (from, to) -> viewModel.applyMembershipRightsChange(to)).show());
}
}
);
@@ -205,13 +233,16 @@ public class ManageGroupFragment extends Fragment {
viewModel.getEditGroupAttributesRights().observe(getViewLifecycleOwner(), r -> {
if (r != null) {
editGroupAccessValue.setText(r.getString());
- editGroupAccessValue.setOnClickListener(v -> new GroupRightsDialog(context, GroupRightsDialog.Type.ATTRIBUTES, r, (from, to) -> viewModel.applyAttributesRightsChange(to)).show());
+ 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);
});
@@ -228,6 +259,12 @@ public class ManageGroupFragment extends Fragment {
}
};
+ muteNotificationsRow.setOnClickListener(v -> {
+ if (muteNotificationsSwitch.isEnabled()) {
+ muteNotificationsSwitch.toggle();
+ }
+ });
+
viewModel.getMuteState().observe(getViewLifecycleOwner(), muteState -> {
if (muteNotificationsSwitch.isChecked() != muteState.isMuted()) {
muteNotificationsSwitch.setOnCheckedChangeListener(null);
@@ -247,10 +284,10 @@ public class ManageGroupFragment extends Fragment {
});
if (NotificationChannels.supported()) {
- customNotificationsControls.setVisibility(View.VISIBLE);
+ customNotificationsRow.setVisibility(View.VISIBLE);
- customNotificationsButton.setOnClickListener(v -> CustomNotificationsDialogFragment.create(groupId)
- .show(requireFragmentManager(), "CUSTOM_NOTIFICATIONS"));
+ customNotificationsRow.setOnClickListener(v -> CustomNotificationsDialogFragment.create(groupId)
+ .show(requireFragmentManager(), "CUSTOM_NOTIFICATIONS"));
//noinspection CodeBlock2Expr
viewModel.hasCustomNotifications().observe(getViewLifecycleOwner(), hasCustomNotifications -> {
@@ -258,7 +295,7 @@ public class ManageGroupFragment extends Fragment {
: R.string.ManageGroupActivity_off);
});
} else {
- customNotificationsControls.setVisibility(View.GONE);
+ customNotificationsRow.setVisibility(View.GONE);
}
}
@@ -308,7 +345,7 @@ public class ManageGroupFragment extends Fragment {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RETURN_FROM_MEDIA) {
applyMediaCursorFactory();
- } else if (requestCode == PICK_CONTACT) {
+ } else if (requestCode == PICK_CONTACT && data != null) {
List selected = data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS);
viewModel.onAddMembers(selected);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java
index 7bb87744d0..20b1ed88f9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/managegroup/ManageGroupViewModel.java
@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.groups.ui.managegroup;
import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
import android.widget.Toast;
@@ -15,11 +14,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.BlockUnblockDialog;
-import org.thoughtcrime.securesms.ContactSelectionListFragment;
import org.thoughtcrime.securesms.ExpirationDialog;
-import org.thoughtcrime.securesms.GroupCreateActivity;
-import org.thoughtcrime.securesms.PushContactSelectionActivity;
-import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
@@ -30,13 +25,17 @@ import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
+import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.Util;
+import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.List;
public class ManageGroupViewModel extends ViewModel {
+ private static final int MAX_COLLAPSED_MEMBERS = 5;
+
private final Context context;
private final ManageGroupRepository manageGroupRepository;
private final LiveData title;
@@ -54,6 +53,8 @@ public class ManageGroupViewModel extends ViewModel {
private final MutableLiveData groupViewState = new MutableLiveData<>(null);
private final LiveData muteState;
private final LiveData hasCustomNotifications;
+ private final LiveData canCollapseMemberList;
+ private final DefaultValueLiveData memberListCollapseState = new DefaultValueLiveData<>(CollapseState.COLLAPSED);
private ManageGroupViewModel(@NonNull Context context, @NonNull ManageGroupRepository manageGroupRepository) {
this.context = context;
@@ -65,7 +66,12 @@ public class ManageGroupViewModel extends ViewModel {
this.title = liveGroup.getTitle();
this.isAdmin = liveGroup.isSelfAdmin();
- this.members = liveGroup.getFullMembers();
+ this.canCollapseMemberList = LiveDataUtil.combineLatest(memberListCollapseState,
+ Transformations.map(liveGroup.getFullMembers(), m -> m.size() > MAX_COLLAPSED_MEMBERS),
+ (state, hasEnoughMembers) -> state != CollapseState.OPEN && hasEnoughMembers);
+ this.members = LiveDataUtil.combineLatest(liveGroup.getFullMembers(),
+ memberListCollapseState,
+ this::filterMemberList);
this.pendingMemberCount = liveGroup.getPendingMemberCount();
this.memberCountSummary = liveGroup.getMembershipCountDescription(context.getResources());
this.fullMemberCountSummary = liveGroup.getFullMembershipCountDescription(context.getResources());
@@ -148,6 +154,10 @@ public class ManageGroupViewModel extends ViewModel {
return hasCustomNotifications;
}
+ public LiveData getCanCollapseMemberList() {
+ return canCollapseMemberList;
+ }
+
void handleExpirationSelection() {
manageGroupRepository.getRecipient(groupRecipient ->
ExpirationDialog.show(context,
@@ -180,6 +190,20 @@ public class ManageGroupViewModel extends ViewModel {
manageGroupRepository.setMuteUntil(0);
}
+ void revealCollapsedMembers() {
+ memberListCollapseState.setValue(CollapseState.OPEN);
+ }
+
+ private @NonNull List filterMemberList(@NonNull List members,
+ @NonNull CollapseState collapseState)
+ {
+ if (collapseState == CollapseState.COLLAPSED && members.size() > MAX_COLLAPSED_MEMBERS) {
+ return members.subList(0, MAX_COLLAPSED_MEMBERS);
+ } else {
+ return members;
+ }
+ }
+
@WorkerThread
private void showErrorToast(@NonNull ManageGroupRepository.FailureReason e) {
Util.runOnMain(() -> Toast.makeText(context, e.getToastMessage(), Toast.LENGTH_LONG).show());
@@ -230,6 +254,11 @@ public class ManageGroupViewModel extends ViewModel {
}
}
+ private enum CollapseState {
+ OPEN,
+ COLLAPSED
+ }
+
interface CursorFactory {
Cursor create();
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java
index 2a7526c683..d2816a0969 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/ProfileName.java
@@ -9,16 +9,14 @@ import androidx.annotation.VisibleForTesting;
import com.annimon.stream.Stream;
+import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.cjkv.CJKVUtil;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
-import java.nio.charset.StandardCharsets;
-
public final class ProfileName implements Parcelable {
- public static final ProfileName EMPTY = new ProfileName("", "");
-
- private static final int MAX_PART_LENGTH = (ProfileCipher.NAME_PADDED_LENGTH - 1) / 2;
+ public static final ProfileName EMPTY = new ProfileName("", "");
+ public static final int MAX_PART_LENGTH = (ProfileCipher.NAME_PADDED_LENGTH - 1) / 2;
private final String givenName;
private final String familyName;
@@ -94,31 +92,12 @@ public final class ProfileName implements Parcelable {
givenName = givenName == null ? "" : givenName;
familyName = familyName == null ? "" : familyName;
- givenName = trimToFit(givenName .trim());
- familyName = trimToFit(familyName.trim());
+ givenName = StringUtil.trimToFit(givenName.trim(), ProfileName.MAX_PART_LENGTH);
+ familyName = StringUtil.trimToFit(familyName.trim(), ProfileName.MAX_PART_LENGTH);
return new ProfileName(givenName, familyName);
}
- /**
- * Trims a name string to fit into the byte length requirement.
- */
- public static @NonNull String trimToFit(@Nullable String name) {
- if (name == null) return "";
-
- // At least one byte per char, so shorten string to reduce loop
- if (name.length() > ProfileName.MAX_PART_LENGTH) {
- name = name.substring(0, ProfileName.MAX_PART_LENGTH);
- }
-
- // Remove one char at a time until fits in byte allowance
- while (name.getBytes(StandardCharsets.UTF_8).length > ProfileName.MAX_PART_LENGTH) {
- name = name.substring(0, name.length() - 1);
- }
-
- return name;
- }
-
private static @NonNull String getJoinedName(@NonNull String givenName, @NonNull String familyName) {
if (givenName.isEmpty() && familyName.isEmpty()) return "";
else if (givenName.isEmpty()) return familyName;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java
index c8963aaed8..ed9e320958 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/edit/EditProfileFragment.java
@@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.registration.RegistrationUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
+import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -66,6 +67,7 @@ public class EditProfileFragment extends Fragment {
private static final String TAG = Log.tag(EditProfileFragment.class);
private static final String AVATAR_STATE = "avatar";
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
+ private static final int MAX_GROUP_NAME_LENGTH = 32;
private Toolbar toolbar;
private View title;
@@ -130,7 +132,9 @@ public class EditProfileFragment extends Fragment {
initializeProfileName();
initializeUsername();
- requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+ if (groupId == null) {
+ requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+ }
}
@Override
@@ -234,7 +238,7 @@ public class EditProfileFragment extends Fragment {
.execute());
this.givenName .addTextChangedListener(new AfterTextChanged(s -> {
- trimInPlace(s);
+ trimInPlace(s, isEditingGroup);
viewModel.setGivenName(s.toString());
}));
@@ -248,7 +252,7 @@ public class EditProfileFragment extends Fragment {
view.findViewById(R.id.avatar_placeholder).setImageResource(R.drawable.ic_group_outline_40);
} else {
this.familyName.addTextChangedListener(new AfterTextChanged(s -> {
- trimInPlace(s);
+ trimInPlace(s, false);
viewModel.setFamilyName(s.toString());
}));
}
@@ -389,8 +393,10 @@ public class EditProfileFragment extends Fragment {
animation.start();
}
- private static void trimInPlace(Editable s) {
- int trimmedLength = ProfileName.trimToFit(s.toString()).length();
+ private static void trimInPlace(Editable s, boolean isGroup) {
+ int trimmedLength = isGroup ? StringUtil.trimToFit(s.toString(), MAX_GROUP_NAME_LENGTH).length()
+ : StringUtil.trimToFit(s.toString(), ProfileName.MAX_PART_LENGTH).length();
+
if (s.length() > trimmedLength) {
s.delete(trimmedLength, s.length());
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/StringUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/StringUtil.java
new file mode 100644
index 0000000000..b12cb581c8
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/StringUtil.java
@@ -0,0 +1,31 @@
+package org.thoughtcrime.securesms.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.nio.charset.StandardCharsets;
+
+public final class StringUtil {
+
+ private StringUtil() {
+ }
+
+ /**
+ * Trims a name string to fit into the byte length requirement.
+ */
+ public static @NonNull String trimToFit(@Nullable String name, int maxLength) {
+ if (name == null) return "";
+
+ // At least one byte per char, so shorten string to reduce loop
+ if (name.length() > maxLength) {
+ name = name.substring(0, maxLength);
+ }
+
+ // Remove one char at a time until fits in byte allowance
+ while (name.getBytes(StandardCharsets.UTF_8).length > maxLength) {
+ name = name.substring(0, name.length() - 1);
+ }
+
+ return name;
+ }
+}
diff --git a/app/src/main/res/drawable/ic_add_members_20.xml b/app/src/main/res/drawable/ic_add_members_20.xml
new file mode 100644
index 0000000000..88cf2df945
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_members_20.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_add_members_circle_dark.xml b/app/src/main/res/drawable/ic_add_members_circle_dark.xml
index 3721f6ab4f..9ec11afa49 100644
--- a/app/src/main/res/drawable/ic_add_members_circle_dark.xml
+++ b/app/src/main/res/drawable/ic_add_members_circle_dark.xml
@@ -7,7 +7,7 @@
diff --git a/app/src/main/res/drawable/ic_add_members_circle_light.xml b/app/src/main/res/drawable/ic_add_members_circle_light.xml
index 95d9af5f76..50991a9380 100644
--- a/app/src/main/res/drawable/ic_add_members_circle_light.xml
+++ b/app/src/main/res/drawable/ic_add_members_circle_light.xml
@@ -7,7 +7,7 @@
diff --git a/app/src/main/res/drawable/ic_plus_24_ultramarine.xml b/app/src/main/res/drawable/ic_plus_24_ultramarine.xml
deleted file mode 100644
index b3a3e6c356..0000000000
--- a/app/src/main/res/drawable/ic_plus_24_ultramarine.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_view_all_20.xml b/app/src/main/res/drawable/ic_view_all_20.xml
new file mode 100644
index 0000000000..47f54f4566
--- /dev/null
+++ b/app/src/main/res/drawable/ic_view_all_20.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_view_all_circle_dark.xml b/app/src/main/res/drawable/ic_view_all_circle_dark.xml
new file mode 100644
index 0000000000..fb46989d4d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_view_all_circle_dark.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_view_all_circle_light.xml b/app/src/main/res/drawable/ic_view_all_circle_light.xml
new file mode 100644
index 0000000000..820436cf27
--- /dev/null
+++ b/app/src/main/res/drawable/ic_view_all_circle_light.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/group_manage_fragment.xml b/app/src/main/res/layout/group_manage_fragment.xml
index 401715c424..2e01a5e5c5 100644
--- a/app/src/main/res/layout/group_manage_fragment.xml
+++ b/app/src/main/res/layout/group_manage_fragment.xml
@@ -27,13 +27,13 @@
@@ -56,28 +57,37 @@
android:id="@+id/group_disappearing_messages_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_title_card">
+ android:layout_height="@dimen/group_manage_fragment_row_height"
+ android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:paddingEnd="@dimen/group_manage_fragment_row_horizontal_padding">
-
+ android:gravity="center_vertical"
+ android:text="@string/ManageGroupActivity_disappearing_messages"
+ android:textAppearance="@style/TextAppearance.Signal.Body2" />
-
@@ -88,81 +98,99 @@
android:id="@+id/group_notifications_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_disappearing_messages_card">
-
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
@@ -170,7 +198,7 @@
android:id="@+id/group_media_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
android:visibility="gone"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_notifications_card"
@@ -204,7 +232,7 @@
android:id="@+id/group_access_control_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
android:visibility="gone"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_media_card"
@@ -216,43 +244,59 @@
android:orientation="vertical">
+ android:layout_height="@dimen/group_manage_fragment_row_height"
+ android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingStart="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:paddingEnd="@dimen/group_manage_fragment_row_horizontal_padding">
-
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|start"
+ android:text="@string/ManageGroupActivity_who_can_edit_group_membership"
+ android:textAppearance="@style/TextAppearance.Signal.Body2" />
-
+ android:layout_height="@dimen/group_manage_fragment_row_height"
+ android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:paddingStart="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:paddingEnd="@dimen/group_manage_fragment_row_horizontal_padding">
-
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|start"
+ android:text="@string/ManageGroupActivity_who_can_edit_group_info"
+ android:textAppearance="@style/TextAppearance.Signal.Body2" />
-
@@ -264,7 +308,7 @@
android:id="@+id/group_membership_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_access_control_card">
@@ -275,32 +319,53 @@
-
+
+
@@ -309,7 +374,7 @@
android:id="@+id/group_pending_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_membership_card">
@@ -326,7 +391,7 @@
android:id="@+id/group_block_and_leave_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/group_manage_fragment_card_vertical_padding"
app:cardBackgroundColor="?android:attr/windowBackground"
app:layout_constraintTop_toBottomOf="@id/group_pending_card">
@@ -335,19 +400,29 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/group_manage_fragment_row_height"
+ android:background="?selectableItemBackground"
+ android:gravity="center_vertical|start"
+ android:paddingStart="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:paddingEnd="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:text="@string/ManageGroupActivity_block_group"
+ android:textAppearance="@style/TextAppearance.Signal.Body2"
+ android:textColor="@color/core_red" />
-
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/group_manage_fragment_row_height"
+ android:background="?selectableItemBackground"
+ android:gravity="center_vertical|start"
+ android:paddingStart="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:paddingEnd="@dimen/group_manage_fragment_row_horizontal_padding"
+ android:text="@string/ManageGroupActivity_leave_group"
+ android:textAppearance="@style/TextAppearance.Signal.Body2"
+ android:textColor="@color/core_red" />
diff --git a/app/src/main/res/layout/group_recipient_list_item.xml b/app/src/main/res/layout/group_recipient_list_item.xml
index 8c09691d23..769d12e65e 100644
--- a/app/src/main/res/layout/group_recipient_list_item.xml
+++ b/app/src/main/res/layout/group_recipient_list_item.xml
@@ -3,7 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="64dp">
+ android:layout_height="64dp"
+ android:background="?selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true">
@@ -75,18 +77,20 @@
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 6d1a996384..0592d9e044 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -159,4 +159,7 @@
340dp
36dp
+ 12dp
+ 52dp
+ 16dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0776f238e0..ece6053059 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -500,6 +500,7 @@
Off
On
Add members
+ View all members
You don\'t have the rights to do this
Someone you added does not support new groups and needs to update Signal
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 631174b38b..387a7fae1d 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -416,12 +416,13 @@