Create a new manage profile screen.
This commit is contained in:
parent
7e64d57ba8
commit
8ca54bcc7b
30 changed files with 1004 additions and 158 deletions
|
@ -460,6 +460,10 @@
|
|||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".profiles.manage.ManageProfileActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize" />
|
||||
|
||||
<activity android:name=".lock.v2.CreateKbsPinActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
|||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.UsernamePreference;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
|
@ -345,7 +346,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
requireActivity().startActivity(EditProfileActivity.getIntentForUserProfileEdit(preference.getContext()));
|
||||
requireActivity().startActivity(ManageProfileActivity.getIntent(requireActivity()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -353,7 +354,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||
private class UsernameClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
requireActivity().startActivity(EditProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||
requireActivity().startActivity(ManageProfileActivity.getIntentForUsernameEdit(preference.getContext()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import java.util.Objects;
|
|||
public final class ProfileName implements Parcelable {
|
||||
|
||||
public static final ProfileName EMPTY = new ProfileName("", "");
|
||||
public static final int MAX_PART_LENGTH = (ProfileCipher.NAME_PADDED_LENGTH - 1) / 2;
|
||||
public static final int MAX_PART_LENGTH = (ProfileCipher.MAX_POSSIBLE_NAME_LENGTH - 1) / 2;
|
||||
|
||||
private final String givenName;
|
||||
private final String familyName;
|
||||
|
|
|
@ -17,16 +17,17 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
|||
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
/**
|
||||
* Shows editing screen for your profile during registration. Also handles group name editing.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class EditProfileActivity extends BaseActivity implements EditProfileFragment.Controller {
|
||||
|
||||
public static final String NEXT_INTENT = "next_intent";
|
||||
public static final String EXCLUDE_SYSTEM = "exclude_system";
|
||||
public static final String DISPLAY_USERNAME = "display_username";
|
||||
public static final String NEXT_BUTTON_TEXT = "next_button_text";
|
||||
public static final String SHOW_TOOLBAR = "show_back_arrow";
|
||||
public static final String GROUP_ID = "group_id";
|
||||
public static final String START_AT_USERNAME = "start_at_username";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
|
||||
|
||||
|
@ -39,7 +40,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
|
|||
public static @NonNull Intent getIntentForUserProfileEdit(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, EditProfileActivity.class);
|
||||
intent.putExtra(EditProfileActivity.EXCLUDE_SYSTEM, true);
|
||||
intent.putExtra(EditProfileActivity.DISPLAY_USERNAME, true);
|
||||
intent.putExtra(EditProfileActivity.NEXT_BUTTON_TEXT, R.string.save);
|
||||
return intent;
|
||||
}
|
||||
|
@ -52,14 +52,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
|
|||
return intent;
|
||||
}
|
||||
|
||||
public static @NonNull Intent getIntentForUsernameEdit(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, EditProfileActivity.class);
|
||||
intent.putExtra(EditProfileActivity.SHOW_TOOLBAR, true);
|
||||
intent.putExtra(EditProfileActivity.DISPLAY_USERNAME, true);
|
||||
intent.putExtra(EditProfileActivity.START_AT_USERNAME, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
@ -73,13 +65,6 @@ public class EditProfileActivity extends BaseActivity implements EditProfileFrag
|
|||
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
|
||||
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, extras != null ? extras : new Bundle());
|
||||
|
||||
if (extras != null &&
|
||||
extras.getBoolean(DISPLAY_USERNAME, false) &&
|
||||
extras.getBoolean(START_AT_USERNAME, false)) {
|
||||
NavDirections action = EditProfileFragmentDirections.actionEditUsername();
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,11 +18,8 @@ import android.widget.Toast;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.dd.CircularProgressButton;
|
||||
|
@ -39,6 +36,7 @@ import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFrag
|
|||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
|
@ -53,7 +51,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.DISPLAY_USERNAME;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.EXCLUDE_SYSTEM;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.GROUP_ID;
|
||||
import static org.thoughtcrime.securesms.profiles.edit.EditProfileActivity.NEXT_BUTTON_TEXT;
|
||||
|
@ -73,9 +70,6 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
private EditText familyName;
|
||||
private View reveal;
|
||||
private TextView preview;
|
||||
private View usernameLabel;
|
||||
private View usernameEditButton;
|
||||
private TextView username;
|
||||
|
||||
private Intent nextIntent;
|
||||
|
||||
|
@ -94,26 +88,8 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
}
|
||||
}
|
||||
|
||||
public static EditProfileFragment create(boolean excludeSystem,
|
||||
Intent nextIntent,
|
||||
boolean displayUsernameField,
|
||||
@StringRes int nextButtonText) {
|
||||
|
||||
EditProfileFragment fragment = new EditProfileFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putBoolean(EXCLUDE_SYSTEM, excludeSystem);
|
||||
args.putParcelable(NEXT_INTENT, nextIntent);
|
||||
args.putBoolean(DISPLAY_USERNAME, displayUsernameField);
|
||||
args.putInt(NEXT_BUTTON_TEXT, nextButtonText);
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.profile_create_fragment, container, false);
|
||||
}
|
||||
|
||||
|
@ -125,13 +101,6 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
initializeViewModel(requireArguments().getBoolean(EXCLUDE_SYSTEM, false), groupId, savedInstanceState != null);
|
||||
initializeProfileAvatar();
|
||||
initializeProfileName();
|
||||
initializeUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
viewModel.refreshUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -200,17 +169,8 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
this.finishButton = view.findViewById(R.id.finish_button);
|
||||
this.reveal = view.findViewById(R.id.reveal);
|
||||
this.preview = view.findViewById(R.id.name_preview);
|
||||
this.username = view.findViewById(R.id.profile_overview_username);
|
||||
this.usernameEditButton = view.findViewById(R.id.profile_overview_username_edit_button);
|
||||
this.usernameLabel = view.findViewById(R.id.profile_overview_username_label);
|
||||
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
|
||||
|
||||
if (FeatureFlags.usernames() && arguments.getBoolean(DISPLAY_USERNAME, false)) {
|
||||
username.setVisibility(View.VISIBLE);
|
||||
usernameEditButton.setVisibility(View.VISIBLE);
|
||||
usernameLabel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
this.avatar.setOnClickListener(v -> startAvatarSelection());
|
||||
|
||||
view.findViewById(R.id.mms_group_hint)
|
||||
|
@ -228,12 +188,14 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
view.findViewById(R.id.description_text).setVisibility(View.GONE);
|
||||
view.<ImageView>findViewById(R.id.avatar_placeholder).setImageResource(R.drawable.ic_group_outline_40);
|
||||
} else {
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(givenName, EditProfileNameFragment.NAME_MAX_GLYPHS);
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(familyName, EditProfileNameFragment.NAME_MAX_GLYPHS);
|
||||
this.givenName.addTextChangedListener(new AfterTextChanged(s -> {
|
||||
trimInPlace(s);
|
||||
EditProfileNameFragment.trimFieldToMaxByteLength(s);
|
||||
viewModel.setGivenName(s.toString());
|
||||
}));
|
||||
this.familyName.addTextChangedListener(new AfterTextChanged(s -> {
|
||||
trimInPlace(s);
|
||||
EditProfileNameFragment.trimFieldToMaxByteLength(s);
|
||||
viewModel.setFamilyName(s.toString());
|
||||
}));
|
||||
LearnMoreTextView descriptionText = view.findViewById(R.id.description_text);
|
||||
|
@ -249,11 +211,6 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
|
||||
this.finishButton.setText(arguments.getInt(NEXT_BUTTON_TEXT, R.string.CreateProfileActivity_next));
|
||||
|
||||
this.usernameEditButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditProfileFragmentDirections.actionEditUsername();
|
||||
Navigation.findNavController(v).navigate(action);
|
||||
});
|
||||
|
||||
if (arguments.getBoolean(SHOW_TOOLBAR, true)) {
|
||||
this.toolbar.setVisibility(View.VISIBLE);
|
||||
this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
|
||||
|
@ -285,10 +242,6 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
});
|
||||
}
|
||||
|
||||
private void initializeUsername() {
|
||||
viewModel.username().observe(getViewLifecycleOwner(), this::onUsernameChanged);
|
||||
}
|
||||
|
||||
private static void updateFieldIfNeeded(@NonNull EditText field, @NonNull String value) {
|
||||
String fieldTrimmed = field.getText().toString().trim();
|
||||
String valueTrimmed = value.trim();
|
||||
|
@ -304,10 +257,6 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private void onUsernameChanged(@NonNull Optional<String> username) {
|
||||
this.username.setText(username.transform(s -> "@" + s).or(""));
|
||||
}
|
||||
|
||||
private void startAvatarSelection() {
|
||||
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveProfilePhoto(),
|
||||
true,
|
||||
|
@ -375,14 +324,6 @@ public class EditProfileFragment extends LoggingFragment {
|
|||
animation.start();
|
||||
}
|
||||
|
||||
private static void trimInPlace(Editable s) {
|
||||
int trimmedLength = StringUtil.trimToFit(s.toString(), ProfileName.MAX_PART_LENGTH).length();
|
||||
|
||||
if (s.length() > trimmedLength) {
|
||||
s.delete(trimmedLength, s.length());
|
||||
}
|
||||
}
|
||||
|
||||
public interface Controller {
|
||||
void onProfileNameUploadCompleted();
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ class EditProfileViewModel extends ViewModel {
|
|||
private final LiveData<ProfileName> internalProfileName = LiveDataUtil.combineLatest(trimmedGivenName, trimmedFamilyName, ProfileName::fromParts);
|
||||
private final MutableLiveData<byte[]> internalAvatar = new MutableLiveData<>();
|
||||
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;
|
||||
private final EditProfileRepository repository;
|
||||
|
@ -77,10 +76,6 @@ class EditProfileViewModel extends ViewModel {
|
|||
return Transformations.distinctUntilChanged(internalAvatar);
|
||||
}
|
||||
|
||||
public LiveData<Optional<String>> username() {
|
||||
return internalUsername;
|
||||
}
|
||||
|
||||
public boolean hasAvatar() {
|
||||
return internalAvatar.getValue() != null;
|
||||
}
|
||||
|
@ -105,10 +100,6 @@ class EditProfileViewModel extends ViewModel {
|
|||
internalAvatar.setValue(avatar);
|
||||
}
|
||||
|
||||
public void refreshUsername() {
|
||||
repository.getCurrentUsername(internalUsername::postValue);
|
||||
}
|
||||
|
||||
public void submitProfile(Consumer<EditProfileRepository.UploadResult> uploadResultConsumer) {
|
||||
ProfileName profileName = isGroup() ? ProfileName.EMPTY : internalProfileName.getValue();
|
||||
String displayName = isGroup() ? givenName.getValue() : "";
|
||||
|
|
|
@ -29,7 +29,7 @@ import java.io.IOException;
|
|||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
class EditSelfProfileRepository implements EditProfileRepository {
|
||||
public class EditSelfProfileRepository implements EditProfileRepository {
|
||||
|
||||
private static final String TAG = Log.tag(EditSelfProfileRepository.class);
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.signal.core.util.EditTextUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
|
||||
/**
|
||||
* Simple fragment to edit your profile name.
|
||||
*/
|
||||
public class EditProfileNameFragment extends Fragment {
|
||||
|
||||
public static final int NAME_MAX_GLYPHS = 26;
|
||||
|
||||
private EditText givenName;
|
||||
private EditText familyName;
|
||||
|
||||
@Override
|
||||
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.edit_profile_name_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.givenName = view.findViewById(R.id.edit_profile_name_given_name);
|
||||
this.familyName = view.findViewById(R.id.edit_profile_name_family_name);
|
||||
|
||||
this.givenName.setText(Recipient.self().getProfileName().getGivenName());
|
||||
this.familyName.setText(Recipient.self().getProfileName().getFamilyName());
|
||||
|
||||
view.<Toolbar>findViewById(R.id.toolbar)
|
||||
.setNavigationOnClickListener(v -> Navigation.findNavController(view)
|
||||
.popBackStack());
|
||||
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(givenName, NAME_MAX_GLYPHS);
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(familyName, NAME_MAX_GLYPHS);
|
||||
|
||||
this.givenName.addTextChangedListener(new AfterTextChanged(EditProfileNameFragment::trimFieldToMaxByteLength));
|
||||
this.familyName.addTextChangedListener(new AfterTextChanged(EditProfileNameFragment::trimFieldToMaxByteLength));
|
||||
|
||||
view.findViewById(R.id.edit_profile_name_save).setOnClickListener(this::onSaveClicked);
|
||||
}
|
||||
|
||||
private void onSaveClicked(View view) {
|
||||
ProfileName profileName = ProfileName.fromParts(givenName.getText().toString(), familyName.getText().toString());
|
||||
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||
DatabaseFactory.getRecipientDatabase(requireContext()).setProfileName(Recipient.self().getId(), profileName);
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
return null;
|
||||
}, (nothing) -> {
|
||||
Navigation.findNavController(view).popBackStack();
|
||||
});
|
||||
}
|
||||
|
||||
public static void trimFieldToMaxByteLength(Editable s) {
|
||||
int trimmedLength = StringUtil.trimToFit(s.toString(), ProfileName.MAX_PART_LENGTH).length();
|
||||
|
||||
if (s.length() > trimmedLength) {
|
||||
s.delete(trimmedLength, s.length());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.NavGraph;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.BaseActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileFragmentDirections;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
/**
|
||||
* Activity that manages the local user's profile, as accessed via the settings.
|
||||
*/
|
||||
public class ManageProfileActivity extends BaseActivity {
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
public static final String START_AT_USERNAME = "start_at_username";
|
||||
|
||||
public static @NonNull Intent getIntent(@NonNull Context context) {
|
||||
return new Intent(context, ManageProfileActivity.class);
|
||||
}
|
||||
|
||||
public static @NonNull Intent getIntentForUsernameEdit(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, ManageProfileActivity.class);
|
||||
intent.putExtra(START_AT_USERNAME, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
dynamicTheme.onCreate(this);
|
||||
|
||||
setContentView(R.layout.manage_profile_activity);
|
||||
|
||||
if (bundle == null) {
|
||||
Bundle extras = getIntent().getExtras();
|
||||
NavGraph graph = Navigation.findNavController(this, R.id.nav_host_fragment).getGraph();
|
||||
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).setGraph(graph, extras != null ? extras : new Bundle());
|
||||
|
||||
if (extras != null && extras.getBoolean(START_AT_USERNAME, false)) {
|
||||
NavDirections action = ManageProfileFragmentDirections.actionManageUsername();
|
||||
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
|
||||
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarState;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
public class ManageProfileFragment extends LoggingFragment {
|
||||
|
||||
private static final String TAG = Log.tag(ManageProfileFragment.class);
|
||||
private static final short REQUEST_CODE_SELECT_AVATAR = 31726;
|
||||
|
||||
private Toolbar toolbar;
|
||||
private ImageView avatarView;
|
||||
private View avatarPlaceholderView;
|
||||
private TextView profileNameView;
|
||||
private View profileNameContainer;
|
||||
private TextView usernameView;
|
||||
private View usernameContainer;
|
||||
private TextView aboutView;
|
||||
private View aboutContainer;
|
||||
private TextView aboutEmojiView;
|
||||
private AlertDialog avatarProgress;
|
||||
|
||||
private ManageProfileViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.manage_profile_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.toolbar = view.findViewById(R.id.toolbar);
|
||||
this.avatarView = view.findViewById(R.id.manage_profile_avatar);
|
||||
this.avatarPlaceholderView = view.findViewById(R.id.manage_profile_avatar_placeholder);
|
||||
this.profileNameView = view.findViewById(R.id.manage_profile_name);
|
||||
this.profileNameContainer = view.findViewById(R.id.manage_profile_name_container);
|
||||
this.usernameView = view.findViewById(R.id.manage_profile_username);
|
||||
this.usernameContainer = view.findViewById(R.id.manage_profile_username_container);
|
||||
this.aboutView = view.findViewById(R.id.manage_profile_about);
|
||||
this.aboutContainer = view.findViewById(R.id.manage_profile_about_container);
|
||||
|
||||
initializeViewModel();
|
||||
|
||||
this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
|
||||
this.avatarView.setOnClickListener(v -> onAvatarClicked());
|
||||
|
||||
this.profileNameContainer.setOnClickListener(v -> {
|
||||
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageProfileName());
|
||||
});
|
||||
|
||||
this.usernameContainer.setOnClickListener(v -> {
|
||||
Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageUsername());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_CODE_SELECT_AVATAR && resultCode == RESULT_OK) {
|
||||
if (data != null && data.getBooleanExtra("delete", false)) {
|
||||
viewModel.onAvatarSelected(requireContext(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
|
||||
|
||||
viewModel.onAvatarSelected(requireContext(), result);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeViewModel() {
|
||||
viewModel = ViewModelProviders.of(this, new ManageProfileViewModel.Factory()).get(ManageProfileViewModel.class);
|
||||
|
||||
viewModel.getAvatar().observe(getViewLifecycleOwner(), this::presentAvatar);
|
||||
viewModel.getProfileName().observe(getViewLifecycleOwner(), this::presentProfileName);
|
||||
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
|
||||
|
||||
if (viewModel.shouldShowAbout()) {
|
||||
viewModel.getAbout().observe(getViewLifecycleOwner(), this::presentAbout);
|
||||
viewModel.getAboutEmoji().observe(getViewLifecycleOwner(), this::presentAboutEmoji);
|
||||
} else {
|
||||
aboutContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (viewModel.shouldShowUsername()) {
|
||||
viewModel.getUsername().observe(getViewLifecycleOwner(), this::presentUsername);
|
||||
} else {
|
||||
usernameContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAvatar(@NonNull AvatarState avatarState) {
|
||||
if (avatarState.getAvatar() == null) {
|
||||
avatarView.setImageDrawable(null);
|
||||
avatarPlaceholderView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
avatarPlaceholderView.setVisibility(View.GONE);
|
||||
Glide.with(this)
|
||||
.load(avatarState.getAvatar())
|
||||
.circleCrop()
|
||||
.into(avatarView);
|
||||
}
|
||||
|
||||
if (avatarProgress == null && avatarState.getLoadingState() == ManageProfileViewModel.LoadingState.LOADING) {
|
||||
avatarProgress = SimpleProgressDialog.show(requireContext());
|
||||
} else if (avatarProgress != null && avatarState.getLoadingState() == ManageProfileViewModel.LoadingState.LOADED) {
|
||||
avatarProgress.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void presentProfileName(@Nullable ProfileName profileName) {
|
||||
if (profileName == null || profileName.isEmpty()) {
|
||||
profileNameView.setText(R.string.ManageProfileFragment_profile_name);
|
||||
} else {
|
||||
profileNameView.setText(profileName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void presentUsername(@Nullable String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
usernameView.setText(R.string.ManageProfileFragment_username);
|
||||
usernameView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_secondary));
|
||||
} else {
|
||||
usernameView.setText(username);
|
||||
usernameView.setTextColor(requireContext().getResources().getColor(R.color.signal_text_primary));
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAbout(@Nullable String about) {
|
||||
if (about == null || about.isEmpty()) {
|
||||
aboutView.setHint(R.string.ManageProfileFragment_about);
|
||||
} else {
|
||||
aboutView.setText(about);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentAboutEmoji(@NonNull String aboutEmoji) {
|
||||
|
||||
}
|
||||
|
||||
private void presentEvent(@NonNull ManageProfileViewModel.Event event) {
|
||||
if (event == ManageProfileViewModel.Event.AVATAR_FAILURE) {
|
||||
Toast.makeText(requireContext(), R.string.ManageProfileFragment_failed_to_set_avatar, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAvatarClicked() {
|
||||
AvatarSelectionBottomSheetDialogFragment.create(viewModel.canRemoveAvatar(),
|
||||
true,
|
||||
REQUEST_CODE_SELECT_AVATAR,
|
||||
false)
|
||||
.show(getChildFragmentManager(), null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
class ManageProfileViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = Log.tag(ManageProfileViewModel.class);
|
||||
|
||||
private final MutableLiveData<AvatarState> avatar;
|
||||
private final MutableLiveData<ProfileName> profileName;
|
||||
private final MutableLiveData<String> username;
|
||||
private final MutableLiveData<String> about;
|
||||
private final MutableLiveData<String> aboutEmoji;
|
||||
private final SingleLiveEvent<Event> events;
|
||||
private final RecipientForeverObserver observer;
|
||||
|
||||
public ManageProfileViewModel() {
|
||||
this.avatar = new MutableLiveData<>();
|
||||
this.profileName = new MutableLiveData<>();
|
||||
this.username = new MutableLiveData<>();
|
||||
this.about = new MutableLiveData<>();
|
||||
this.aboutEmoji = new MutableLiveData<>();
|
||||
this.events = new SingleLiveEvent<>();
|
||||
this.observer = this::onRecipientChanged;
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
onRecipientChanged(Recipient.self().fresh());
|
||||
|
||||
StreamDetails details = AvatarHelper.getSelfProfileAvatarStream(ApplicationDependencies.getApplication());
|
||||
if (details != null) {
|
||||
try {
|
||||
avatar.postValue(AvatarState.loaded(StreamUtil.readFully(details.getStream())));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to read avatar!");
|
||||
avatar.postValue(AvatarState.none());
|
||||
}
|
||||
} else {
|
||||
avatar.postValue(AvatarState.none());
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RetrieveProfileJob.forRecipient(Recipient.self().getId()));
|
||||
});
|
||||
|
||||
Recipient.self().live().observeForever(observer);
|
||||
}
|
||||
|
||||
public @NonNull LiveData<AvatarState> getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<ProfileName> getProfileName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<String> getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<String> getAbout() {
|
||||
return about;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<String> getAboutEmoji() {
|
||||
return aboutEmoji;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<Event> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public boolean shouldShowUsername() {
|
||||
return FeatureFlags.usernames();
|
||||
}
|
||||
|
||||
public boolean shouldShowAbout() {
|
||||
return FeatureFlags.about();
|
||||
}
|
||||
|
||||
public void onAvatarSelected(@NonNull Context context, @Nullable Media media) {
|
||||
if (media == null) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
AvatarHelper.delete(context, Recipient.self().getId());
|
||||
avatar.postValue(AvatarState.none());
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
});
|
||||
} else {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
try {
|
||||
InputStream stream = BlobProvider.getInstance().getStream(context, media.getUri());
|
||||
byte[] data = StreamUtil.readFully(stream);
|
||||
|
||||
AvatarHelper.setAvatar(context, Recipient.self().getId(), new ByteArrayInputStream(data));
|
||||
avatar.postValue(AvatarState.loaded(data));
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to save avatar!", e);
|
||||
events.postValue(Event.AVATAR_FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canRemoveAvatar() {
|
||||
return avatar.getValue() != null;
|
||||
}
|
||||
|
||||
private void onRecipientChanged(@NonNull Recipient recipient) {
|
||||
profileName.postValue(recipient.getProfileName());
|
||||
username.postValue(recipient.getUsername().orNull());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
Recipient.self().live().removeForeverObserver(observer);
|
||||
}
|
||||
|
||||
public static class AvatarState {
|
||||
private final byte[] avatar;
|
||||
private final LoadingState loadingState;
|
||||
|
||||
public AvatarState(@Nullable byte[] avatar, @NonNull LoadingState loadingState) {
|
||||
this.avatar = avatar;
|
||||
this.loadingState = loadingState;
|
||||
}
|
||||
|
||||
private static @NonNull AvatarState none() {
|
||||
return new AvatarState(null, LoadingState.LOADED);
|
||||
}
|
||||
|
||||
private static @NonNull AvatarState loaded(@Nullable byte[] avatar) {
|
||||
return new AvatarState(avatar, LoadingState.LOADED);
|
||||
}
|
||||
|
||||
private static @NonNull AvatarState loading(@Nullable byte[] avatar) {
|
||||
return new AvatarState(avatar, LoadingState.LOADING);
|
||||
}
|
||||
|
||||
public @Nullable byte[] getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public LoadingState getLoadingState() {
|
||||
return loadingState;
|
||||
}
|
||||
}
|
||||
|
||||
public enum LoadingState {
|
||||
LOADING, LOADED
|
||||
}
|
||||
|
||||
enum Event {
|
||||
AVATAR_FAILURE
|
||||
}
|
||||
|
||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||
@Override
|
||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
return Objects.requireNonNull(modelClass.cast(new ManageProfileViewModel()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.thoughtcrime.securesms.usernames.username;
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
|
@ -1,4 +1,4 @@
|
|||
package org.thoughtcrime.securesms.usernames.username;
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.app.Application;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.thoughtcrime.securesms.usernames.username;
|
||||
package org.thoughtcrime.securesms.profiles.manage;
|
||||
|
||||
import android.app.Application;
|
||||
import android.text.TextUtils;
|
|
@ -71,6 +71,7 @@ public final class FeatureFlags {
|
|||
private static final String AUTOMATIC_SESSION_INTERVAL = "android.automaticSessionResetInterval";
|
||||
private static final String DEFAULT_MAX_BACKOFF = "android.defaultMaxBackoff";
|
||||
private static final String OKHTTP_AUTOMATIC_RETRY = "android.okhttpAutomaticRetry";
|
||||
private static final String ABOUT = "android.about";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -98,7 +99,8 @@ public final class FeatureFlags {
|
|||
AUTOMATIC_SESSION_RESET,
|
||||
AUTOMATIC_SESSION_INTERVAL,
|
||||
DEFAULT_MAX_BACKOFF,
|
||||
OKHTTP_AUTOMATIC_RETRY
|
||||
OKHTTP_AUTOMATIC_RETRY,
|
||||
ABOUT
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -136,7 +138,8 @@ public final class FeatureFlags {
|
|||
AUTOMATIC_SESSION_RESET,
|
||||
AUTOMATIC_SESSION_INTERVAL,
|
||||
DEFAULT_MAX_BACKOFF,
|
||||
OKHTTP_AUTOMATIC_RETRY
|
||||
OKHTTP_AUTOMATIC_RETRY,
|
||||
ABOUT
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -316,6 +319,11 @@ public final class FeatureFlags {
|
|||
return getBoolean(OKHTTP_AUTOMATIC_RETRY, false);
|
||||
}
|
||||
|
||||
/** Whether or not the 'About' section of the profile is enabled. */
|
||||
public static boolean about() {
|
||||
return getBoolean(ABOUT, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
|
9
app/src/main/res/drawable-night/ic_compose_24.xml
Normal file
9
app/src/main/res/drawable-night/ic_compose_24.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M21.561,6.682 L19.086,9.157 14.843,4.914l2.475,-2.475a1.5,1.5 0,0 1,2.121 0l2.122,2.122A1.5,1.5 0,0 1,21.561 6.682ZM3.429,16.631 L2.317,21.076a0.5,0.5 0,0 0,0.607 0.607l4.445,-1.112a1.5,1.5 0,0 0,0.7 -0.394l9.959,-9.959L13.782,5.975 3.823,15.934A1.5,1.5 0,0 0,3.429 16.631Z"/>
|
||||
</vector>
|
10
app/src/main/res/drawable-night/ic_profile_name_24.xml
Normal file
10
app/src/main/res/drawable-night/ic_profile_name_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,2.5C11.0258,2.5 10.0734,2.7943 9.2633,3.3458C8.4533,3.8973 7.8219,4.6811 7.4491,5.5982C7.0763,6.5153 6.9787,7.5244 7.1688,8.498C7.3588,9.4716 7.828,10.3658 8.5169,11.0677C9.2058,11.7696 10.0835,12.2476 11.039,12.4413C11.9946,12.635 12.985,12.5356 13.8851,12.1557C14.7852,11.7758 15.5545,11.1326 16.0958,10.3072C16.6371,9.4818 16.926,8.5115 16.926,7.5189C16.926,6.1878 16.407,4.9112 15.4832,3.97C14.5594,3.0288 13.3065,2.5 12,2.5ZM16.9808,12.5765C16.3283,13.2458 15.5523,13.777 14.6975,14.1395C13.8427,14.502 12.9259,14.6886 12,14.6886C11.0741,14.6886 10.1573,14.502 9.3025,14.1395C8.4477,13.777 7.6717,13.2458 7.0192,12.5765C5.7036,13.0321 4.5608,13.8953 3.7504,15.0454C2.9402,16.1955 2.5029,17.5749 2.5,18.9906V20.066C2.5,20.4463 2.6483,20.8111 2.9122,21.08C3.1762,21.3489 3.5341,21.5 3.9074,21.5H20.0926C20.4659,21.5 20.8238,21.3489 21.0878,21.08C21.3517,20.8111 21.5,20.4463 21.5,20.066V18.9906C21.4971,17.5749 21.0599,16.1955 20.2495,15.0454C19.4392,13.8953 18.2964,13.0321 16.9808,12.5765Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_compose_24.xml
Normal file
9
app/src/main/res/drawable/ic_compose_24.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M21.561,4.561 L19.439,2.439a1.5,1.5 0,0 0,-2.121 0L3.823,15.934a1.5,1.5 0,0 0,-0.394 0.7L2.317,21.076a0.5,0.5 0,0 0,0.607 0.607l4.445,-1.112a1.5,1.5 0,0 0,0.7 -0.394l13.5,-13.495A1.5,1.5 0,0 0,21.561 4.561ZM7.005,19.116l-2.828,0.707L4.884,17l9.772,-9.773 2.122,2.122ZM17.838,8.283 L15.717,6.162 18.379,3.5 20.5,5.621Z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_profile_name_24.xml
Normal file
9
app/src/main/res/drawable/ic_profile_name_24.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M16.3836,11.8716C17.1821,11.0172 17.7135,9.9481 17.9125,8.7958C18.1115,7.6434 17.9694,6.458 17.5037,5.3853C17.038,4.3126 16.269,3.3994 15.2912,2.758C14.3134,2.1165 13.1694,1.7748 12,1.7748C10.8306,1.7748 9.6867,2.1165 8.7089,2.758C7.7311,3.3994 6.9621,4.3126 6.4963,5.3853C6.0306,6.458 5.8885,7.6434 6.0875,8.7958C6.2865,9.9481 6.8179,11.0172 7.6164,11.8716C6.1365,12.2089 4.8148,13.0382 3.8673,14.2239C2.9198,15.4097 2.4025,16.8818 2.4,18.3996V20.4H4.2V18.3996C4.2016,17.1006 4.7183,15.8553 5.6368,14.9368C6.5553,14.0183 7.8007,13.5016 9.0996,13.5H14.9004C16.1994,13.5016 17.4447,14.0183 18.3632,14.9368C19.2817,15.8553 19.7984,17.1006 19.8,18.3996V20.4H21.6V18.3996C21.5975,16.8818 21.0802,15.4097 20.1327,14.2239C19.1852,13.0382 17.8635,12.2089 16.3836,11.8716V11.8716ZM12,12C11.1693,12 10.3573,11.7537 9.6666,11.2922C8.9759,10.8307 8.4376,10.1747 8.1197,9.4073C7.8018,8.6398 7.7187,7.7953 7.8807,6.9806C8.0428,6.1659 8.4428,5.4175 9.0302,4.8302C9.6176,4.2428 10.3659,3.8428 11.1806,3.6807C11.9954,3.5186 12.8398,3.6018 13.6073,3.9197C14.3747,4.2376 15.0307,4.7759 15.4922,5.4666C15.9537,6.1573 16.2,6.9693 16.2,7.8C16.2,8.9139 15.7575,9.9822 14.9699,10.7698C14.1822,11.5575 13.1139,12 12,12Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
61
app/src/main/res/layout/edit_profile_name_fragment.xml
Normal file
61
app/src/main/res/layout/edit_profile_name_fragment.xml
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_24"
|
||||
app:title="@string/EditProfileNameFragment_your_name" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
android:id="@+id/edit_profile_name_given_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:hint="@string/EditProfileNameFragment_first_name"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
android:id="@+id/edit_profile_name_family_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:hint="@string/EditProfileNameFragment_last_name_optional"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintTop_toBottomOf="@id/edit_profile_name_given_name"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/edit_profile_name_save"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Signal.Widget.Button.Medium.Primary"
|
||||
android:text="@string/EditProfileNameFragment_save"
|
||||
app:cornerRadius="80dp"
|
||||
app:elevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
21
app/src/main/res/layout/manage_profile_activity.xml
Normal file
21
app/src/main/res/layout/manage_profile_activity.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".profiles.edit.EditProfileActivity">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/manage_profile" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
227
app/src/main/res/layout/manage_profile_fragment.xml
Normal file
227
app/src/main/res/layout/manage_profile_fragment.xml
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_24"
|
||||
app:title="@string/CreateProfileActivity__profile" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/manage_profile_avatar_background"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_marginTop="33dp"
|
||||
android:src="@drawable/circle_tintable"
|
||||
android:tint="@color/core_grey_05"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_goneMarginTop="?attr/actionBarSize" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/manage_profile_avatar_placeholder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:tint="@color/core_grey_75"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintEnd_toEndOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background"
|
||||
app:srcCompat="@drawable/ic_profile_outline_40" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_avatar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/CreateProfileActivity_set_avatar_description"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintEnd_toEndOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_camera_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginTop="56dp"
|
||||
android:background="@drawable/circle_tintable_padded"
|
||||
android:cropToPadding="false"
|
||||
android:elevation="4dp"
|
||||
android:padding="14dp"
|
||||
app:backgroundTint="@color/camera_icon_background_tint"
|
||||
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background"
|
||||
app:srcCompat="@drawable/ic_camera_24" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/manage_profile_name_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="33dp"
|
||||
android:paddingStart="26dp"
|
||||
android:paddingEnd="26dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?selectableItemBackground"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_avatar">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_name_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_profile_name_24"
|
||||
app:tint="@color/signal_text_primary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/manage_profile_name"
|
||||
app:layout_constraintBottom_toBottomOf="@id/manage_profile_name_subtitle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="26dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/manage_profile_name_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="Peter Parker"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_name_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:text="@string/ManageProfileFragment_your_name"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_name"
|
||||
app:layout_constraintStart_toStartOf="@id/manage_profile_name"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/manage_profile_username_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="26dp"
|
||||
android:paddingEnd="26dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?selectableItemBackground"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_name_container">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_username_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_at_24"
|
||||
app:tint="@color/signal_text_primary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/manage_profile_username"
|
||||
app:layout_constraintBottom_toBottomOf="@id/manage_profile_username_subtitle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_username"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="26dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/manage_profile_username_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="\@spiderman"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_username_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:text="@string/ManageProfileFragment_your_username"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_username"
|
||||
app:layout_constraintStart_toStartOf="@id/manage_profile_username"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/manage_profile_about_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="26dp"
|
||||
android:paddingEnd="26dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?selectableItemBackground"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_username_container">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_about_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_compose_24"
|
||||
app:tint="@color/signal_text_primary"
|
||||
app:layout_constraintTop_toTopOf="@id/manage_profile_about"
|
||||
app:layout_constraintBottom_toBottomOf="@id/manage_profile_about_subtitle"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_about"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="26dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/manage_profile_about_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:text="Reporter for the Daily Bugle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_about_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:text="@string/ManageProfileFragment_write_a_few_words_about_yourself"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_about"
|
||||
app:layout_constraintStart_toStartOf="@id/manage_profile_about"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||
android:id="@+id/description_text"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="33dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="26dp"
|
||||
android:text="@string/CreateProfileActivity_signal_profiles_are_end_to_end_encrypted"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/manage_profile_about_container"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -165,54 +165,6 @@
|
|||
android:singleLine="true" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/profile_overview_username_label"
|
||||
style="@style/Signal.Text.Caption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/CreateProfileActivity__username"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/name_container"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/profile_overview_username"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@null"
|
||||
android:editable="false"
|
||||
android:focusable="false"
|
||||
android:hint="@string/CreateProfileActivity__create_a_username"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@id/profile_overview_username_edit_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/profile_overview_username_label"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/profile_overview_username_edit_button"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="8dp"
|
||||
android:tint="@color/core_grey_55"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/profile_overview_username"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/profile_overview_username"
|
||||
app:srcCompat="@drawable/ic_compose_solid_24"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
|
||||
android:id="@+id/description_text"
|
||||
style="@style/Signal.Text.Preview"
|
||||
|
@ -226,7 +178,7 @@
|
|||
android:textColor="@color/core_grey_60"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/profile_overview_username_edit_button"
|
||||
app:layout_constraintTop_toBottomOf="@+id/name_container"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
<fragment
|
||||
android:id="@+id/usernameEditFragment"
|
||||
android:name="org.thoughtcrime.securesms.usernames.username.UsernameEditFragment"
|
||||
android:name="org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment"
|
||||
android:label="fragment_edit_username"
|
||||
tools:layout="@layout/username_edit_fragment" />
|
||||
|
||||
|
|
44
app/src/main/res/navigation/manage_profile.xml
Normal file
44
app/src/main/res/navigation/manage_profile.xml
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/manage_profile"
|
||||
app:startDestination="@id/manageProfileFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/manageProfileFragment"
|
||||
android:name="org.thoughtcrime.securesms.profiles.manage.ManageProfileFragment"
|
||||
android:label="fragment_manage_profile"
|
||||
tools:layout="@layout/profile_create_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_manageUsername"
|
||||
app:destination="@id/usernameManageFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_manageProfileName"
|
||||
app:destination="@id/profileNameManageFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/usernameManageFragment"
|
||||
android:name="org.thoughtcrime.securesms.profiles.manage.UsernameEditFragment"
|
||||
android:label="fragment_manage_username"
|
||||
tools:layout="@layout/username_edit_fragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/profileNameManageFragment"
|
||||
android:name="org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment"
|
||||
android:label="fragment_manage_profile_name"
|
||||
tools:layout="@layout/edit_profile_name_fragment" />
|
||||
|
||||
</navigation>
|
|
@ -802,7 +802,16 @@
|
|||
<string name="GroupMentionSettingDialog_default_dont_notify_me">Default (Don\'t notify me)</string>
|
||||
<string name="GroupMentionSettingDialog_always_notify_me">Always notify me</string>
|
||||
<string name="GroupMentionSettingDialog_dont_notify_me">Don\'t notify me</string>
|
||||
|
||||
|
||||
<!-- ManageProfileFragment -->
|
||||
<string name="ManageProfileFragment_profile_name">Profile name</string>
|
||||
<string name="ManageProfileFragment_username">Username</string>
|
||||
<string name="ManageProfileFragment_about">About</string>
|
||||
<string name="ManageProfileFragment_write_a_few_words_about_yourself">Write a few words about yourself</string>
|
||||
<string name="ManageProfileFragment_your_name">Your name</string>
|
||||
<string name="ManageProfileFragment_your_username">Your username</string>
|
||||
<string name="ManageProfileFragment_failed_to_set_avatar">Failed to set avatar</string>
|
||||
|
||||
<!-- ManageRecipientActivity -->
|
||||
<string name="ManageRecipientActivity_add_to_system_contacts">Add to system contacts</string>
|
||||
<string name="ManageRecipientActivity_this_person_is_in_your_contacts">This person is in your contacts</string>
|
||||
|
@ -2177,6 +2186,12 @@
|
|||
<string name="EditProfileFragment__group_name">Group name</string>
|
||||
<string name="EditProfileFragment__support_link" translatable="false">https://support.signal.org/hc/articles/360007459591</string>
|
||||
|
||||
<!-- EditProfileNameFragment -->
|
||||
<string name="EditProfileNameFragment_your_name">Your name</string>
|
||||
<string name="EditProfileNameFragment_first_name">First name</string>
|
||||
<string name="EditProfileNameFragment_last_name_optional">Last name (optional)</string>
|
||||
<string name="EditProfileNameFragment_save">Save</string>
|
||||
|
||||
<!-- recipient_preferences_activity -->
|
||||
<string name="recipient_preference_activity__shared_media">Shared media</string>
|
||||
|
||||
|
|
|
@ -167,10 +167,10 @@ public final class ProfileNameTest {
|
|||
|
||||
@Test
|
||||
public void fromParts_with_long_name_parts() {
|
||||
ProfileName name = ProfileName.fromParts("GivenSomeVeryLongNameSomeVeryLongName", "FamilySomeVeryLongNameSomeVeryLongName");
|
||||
ProfileName name = ProfileName.fromParts("GivenSomeVeryLongNameSomeVeryLongNameGivenSomeVeryLongNameSomeVeryLongNameGivenSomeVeryLongNameSomeVeryLongNameGivenSomeVeryLongNameSomeVeryLongName", "FamilySomeVeryLongNameSomeVeryLongName");
|
||||
|
||||
assertEquals("GivenSomeVeryLongNameSomeV", name.getGivenName());
|
||||
assertEquals("FamilySomeVeryLongNameSome", name.getFamilyName());
|
||||
assertEquals("GivenSomeVeryLongNameSomeVeryLongNameGivenSomeVeryLongNameSomeVeryLongNameGivenSomeVeryLongNameSomeVeryLongNameGivenSomeVeryLong", name.getGivenName());
|
||||
assertEquals("FamilySomeVeryLongNameSomeVeryLongName", name.getFamilyName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -639,7 +639,7 @@ public class SignalServiceAccountManager {
|
|||
{
|
||||
if (name == null) name = "";
|
||||
|
||||
byte[] ciphertextName = new ProfileCipher(profileKey).encryptName(name.getBytes(StandardCharsets.UTF_8), ProfileCipher.NAME_PADDED_LENGTH);
|
||||
byte[] ciphertextName = new ProfileCipher(profileKey).encryptName(name.getBytes(StandardCharsets.UTF_8), ProfileCipher.getTargetNameLength(name));
|
||||
boolean hasAvatar = avatar != null;
|
||||
ProfileAvatarData profileAvatarData = null;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.signal.zkgroup.profiles.ProfileKey;
|
|||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
|
@ -20,7 +21,10 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
|
||||
public class ProfileCipher {
|
||||
|
||||
public static final int NAME_PADDED_LENGTH = 53;
|
||||
private static final int NAME_PADDED_LENGTH_1 = 53;
|
||||
private static final int NAME_PADDED_LENGTH_2 = 257;
|
||||
|
||||
public static final int MAX_POSSIBLE_NAME_LENGTH = NAME_PADDED_LENGTH_2;
|
||||
|
||||
private final ProfileKey key;
|
||||
|
||||
|
@ -99,4 +103,13 @@ public class ProfileCipher {
|
|||
}
|
||||
}
|
||||
|
||||
public static int getTargetNameLength(String name) {
|
||||
int nameLength = name.getBytes(StandardCharsets.UTF_8).length;
|
||||
|
||||
if (nameLength <= NAME_PADDED_LENGTH_1) {
|
||||
return NAME_PADDED_LENGTH_1;
|
||||
} else {
|
||||
return NAME_PADDED_LENGTH_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,11 @@ import org.conscrypt.Conscrypt;
|
|||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Security;
|
||||
|
||||
public class ProfileCipherTest extends TestCase {
|
||||
|
@ -21,7 +23,7 @@ public class ProfileCipherTest extends TestCase {
|
|||
public void testEncryptDecrypt() throws InvalidCiphertextException, InvalidInputException {
|
||||
ProfileKey key = new ProfileKey(Util.getSecretBytes(32));
|
||||
ProfileCipher cipher = new ProfileCipher(key);
|
||||
byte[] name = cipher.encryptName("Clement\0Duval".getBytes(), ProfileCipher.NAME_PADDED_LENGTH);
|
||||
byte[] name = cipher.encryptName("Clement\0Duval".getBytes(), 53);
|
||||
byte[] plaintext = cipher.decryptName(name);
|
||||
assertEquals(new String(plaintext), "Clement\0Duval");
|
||||
}
|
||||
|
@ -59,4 +61,29 @@ public class ProfileCipherTest extends TestCase {
|
|||
assertEquals(new String(result.toByteArray()), "This is an avatar");
|
||||
}
|
||||
|
||||
public void testEncryptLengthBucket1() throws InvalidInputException {
|
||||
ProfileKey key = new ProfileKey(Util.getSecretBytes(32));
|
||||
ProfileCipher cipher = new ProfileCipher(key);
|
||||
byte[] name = cipher.encryptName("Peter\0Parker".getBytes(), 53);
|
||||
|
||||
String encoded = Base64.encodeBytes(name);
|
||||
|
||||
assertEquals(108, encoded.length());
|
||||
}
|
||||
|
||||
public void testEncryptLengthBucket2() throws InvalidInputException {
|
||||
ProfileKey key = new ProfileKey(Util.getSecretBytes(32));
|
||||
ProfileCipher cipher = new ProfileCipher(key);
|
||||
byte[] name = cipher.encryptName("Peter\0Parker".getBytes(), 257);
|
||||
|
||||
String encoded = Base64.encodeBytes(name);
|
||||
|
||||
assertEquals(380, encoded.length());
|
||||
}
|
||||
|
||||
public void testTargetNameLength() {
|
||||
assertEquals(53, ProfileCipher.getTargetNameLength("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"));
|
||||
assertEquals(53, ProfileCipher.getTargetNameLength("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1"));
|
||||
assertEquals(257, ProfileCipher.getTargetNameLength("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue