mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-31 04:06:26 +00:00
Add some polish to the groups V2 manager UI.
This commit is contained in:
@@ -10,6 +10,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
@@ -137,13 +138,17 @@ final class GroupMemberListAdapter extends LifecycleRecyclerAdapter<GroupMemberL
|
||||
}
|
||||
|
||||
void bindRecipientClick(@NonNull Recipient recipient) {
|
||||
View.OnClickListener onClickListener = v -> {
|
||||
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<GroupMemberL
|
||||
admin.setVisibility(View.GONE);
|
||||
hideMenu();
|
||||
|
||||
avatar.setOnClickListener(null);
|
||||
recipient.setOnClickListener(null);
|
||||
itemView.setOnClickListener(null);
|
||||
|
||||
memberEntry.getBusy().observe(this, busy -> {
|
||||
busyProgress.setVisibility(busy ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<RecipientId> selected = data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS);
|
||||
viewModel.onAddMembers(selected);
|
||||
}
|
||||
|
||||
@@ -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<String> title;
|
||||
@@ -54,6 +53,8 @@ public class ManageGroupViewModel extends ViewModel {
|
||||
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 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<Boolean> 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<GroupMemberEntry.FullMember> filterMemberList(@NonNull List<GroupMemberEntry.FullMember> 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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.<ImageView>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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user