Username UX refresh.

fork-5.53.8
Alex Hart 2022-08-16 16:59:12 -03:00 zatwierdzone przez Cody Henthorne
rodzic 3252871ed5
commit 28310a88f5
24 zmienionych plików z 1254 dodań i 589 usunięć

Wyświetl plik

@ -12,14 +12,12 @@ import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation;
@ -28,11 +26,13 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.signal.core.util.EditTextUtil;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.databinding.ProfileCreateFragmentBinding;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ParcelableGroupId;
import org.thoughtcrime.securesms.mediasend.Media;
@ -41,11 +41,8 @@ import org.thoughtcrime.securesms.profiles.manage.EditProfileNameFragment;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.signal.core.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
import java.io.IOException;
import java.io.InputStream;
@ -62,20 +59,10 @@ public class EditProfileFragment extends LoggingFragment {
private static final int MAX_DESCRIPTION_GLYPHS = 480;
private static final int MAX_DESCRIPTION_BYTES = 8192;
private Toolbar toolbar;
private View title;
private ImageView avatar;
private CircularProgressMaterialButton finishButton;
private EditText givenName;
private EditText familyName;
private View reveal;
private TextView preview;
private ImageView avatarPreviewBackground;
private ImageView avatarPreview;
private Intent nextIntent;
private EditProfileViewModel viewModel;
private EditProfileViewModel viewModel;
private ProfileCreateFragmentBinding binding;
private Controller controller;
@ -92,7 +79,8 @@ public class EditProfileFragment extends LoggingFragment {
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.profile_create_fragment, container, false);
binding = ProfileCreateFragmentBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
@ -108,7 +96,7 @@ public class EditProfileFragment extends LoggingFragment {
if (bundle.getBoolean(AvatarPickerFragment.SELECT_AVATAR_CLEAR)) {
viewModel.setAvatarMedia(null);
viewModel.setAvatar(null);
avatar.setImageDrawable(null);
binding.avatar.setImageDrawable(null);
} else {
Media media = bundle.getParcelable(AvatarPickerFragment.SELECT_AVATAR_MEDIA);
handleMediaFromResult(media);
@ -116,6 +104,12 @@ public class EditProfileFragment extends LoggingFragment {
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
private void handleMediaFromResult(@NonNull Media media) {
SimpleTask.run(() -> {
try {
@ -136,7 +130,7 @@ public class EditProfileFragment extends LoggingFragment {
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(avatar);
.into(binding.avatar);
} else {
Toast.makeText(requireActivity(), R.string.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
}
@ -162,111 +156,108 @@ public class EditProfileFragment extends LoggingFragment {
Bundle arguments = requireArguments();
boolean isEditingGroup = groupId != null;
this.toolbar = view.findViewById(R.id.toolbar);
this.title = view.findViewById(R.id.title);
this.avatar = view.findViewById(R.id.avatar);
this.givenName = view.findViewById(R.id.given_name);
this.familyName = view.findViewById(R.id.family_name);
this.finishButton = view.findViewById(R.id.finish_button);
this.reveal = view.findViewById(R.id.reveal);
this.preview = view.findViewById(R.id.name_preview);
this.avatarPreviewBackground = view.findViewById(R.id.avatar_background);
this.avatarPreview = view.findViewById(R.id.avatar_placeholder);
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
this.nextIntent = arguments.getParcelable(NEXT_INTENT);
this.avatar.setOnClickListener(v -> startAvatarSelection());
view.findViewById(R.id.mms_group_hint)
.setVisibility(isEditingGroup && groupId.isMms() ? View.VISIBLE : View.GONE);
binding.avatar.setOnClickListener(v -> startAvatarSelection());
binding.mmsGroupHint.setVisibility(isEditingGroup && groupId.isMms() ? View.VISIBLE : View.GONE);
if (isEditingGroup) {
EditTextUtil.addGraphemeClusterLimitFilter(givenName, FeatureFlags.getMaxGroupNameGraphemeLength());
givenName.addTextChangedListener(new AfterTextChanged(s -> viewModel.setGivenName(s.toString())));
givenName.setHint(R.string.EditProfileFragment__group_name);
givenName.requestFocus();
toolbar.setTitle(R.string.EditProfileFragment__edit_group);
preview.setVisibility(View.GONE);
EditTextUtil.addGraphemeClusterLimitFilter(binding.givenName, FeatureFlags.getMaxGroupNameGraphemeLength());
binding.profileDescriptionText.setVisibility(View.GONE);
binding.whoCanFindMeContainer.setVisibility(View.GONE);
binding.givenName.addTextChangedListener(new AfterTextChanged(s -> viewModel.setGivenName(s.toString())));
binding.givenNameWrapper.setHint(R.string.EditProfileFragment__group_name);
binding.givenName.requestFocus();
binding.toolbar.setTitle(R.string.EditProfileFragment__edit_group);
binding.namePreview.setVisibility(View.GONE);
if (groupId.isV2()) {
EditTextUtil.addGraphemeClusterLimitFilter(familyName, MAX_DESCRIPTION_GLYPHS);
familyName.addTextChangedListener(new AfterTextChanged(s -> {
EditTextUtil.addGraphemeClusterLimitFilter(binding.familyName, MAX_DESCRIPTION_GLYPHS);
binding.familyName.addTextChangedListener(new AfterTextChanged(s -> {
EditProfileNameFragment.trimFieldToMaxByteLength(s, MAX_DESCRIPTION_BYTES);
viewModel.setFamilyName(s.toString());
}));
familyName.setHint(R.string.EditProfileFragment__group_description);
familyName.setSingleLine(false);
familyName.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
binding.familyNameWrapper.setHint(R.string.EditProfileFragment__group_description);
binding.familyName.setSingleLine(false);
binding.familyName.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
LearnMoreTextView descriptionText = view.findViewById(R.id.description_text);
descriptionText.setLearnMoreVisible(false);
descriptionText.setText(R.string.CreateProfileActivity_group_descriptions_will_be_visible_to_members_of_this_group_and_people_who_have_been_invited);
binding.groupDescriptionText.setLearnMoreVisible(false);
binding.groupDescriptionText.setText(R.string.CreateProfileActivity_group_descriptions_will_be_visible_to_members_of_this_group_and_people_who_have_been_invited);
} else {
familyName.setVisibility(View.GONE);
familyName.setEnabled(false);
view.findViewById(R.id.description_text).setVisibility(View.GONE);
binding.familyNameWrapper.setVisibility(View.GONE);
binding.familyName.setEnabled(false);
binding.groupDescriptionText.setVisibility(View.GONE);
}
view.<ImageView>findViewById(R.id.avatar_placeholder).setImageResource(R.drawable.ic_group_outline_40);
binding.avatarPlaceholder.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 -> {
EditTextUtil.addGraphemeClusterLimitFilter(binding.givenName, EditProfileNameFragment.NAME_MAX_GLYPHS);
EditTextUtil.addGraphemeClusterLimitFilter(binding.familyName, EditProfileNameFragment.NAME_MAX_GLYPHS);
binding.givenName.addTextChangedListener(new AfterTextChanged(s -> {
EditProfileNameFragment.trimFieldToMaxByteLength(s);
viewModel.setGivenName(s.toString());
}));
this.familyName.addTextChangedListener(new AfterTextChanged(s -> {
binding.familyName.addTextChangedListener(new AfterTextChanged(s -> {
EditProfileNameFragment.trimFieldToMaxByteLength(s);
viewModel.setFamilyName(s.toString());
}));
LearnMoreTextView descriptionText = view.findViewById(R.id.description_text);
descriptionText.setLearnMoreVisible(true);
descriptionText.setOnLinkClickListener(v -> CommunicationActions.openBrowserLink(requireContext(), getString(R.string.EditProfileFragment__support_link)));
binding.groupDescriptionText.setVisibility(View.GONE);
binding.profileDescriptionText.setLearnMoreVisible(true);
binding.profileDescriptionText.setLinkColor(ContextCompat.getColor(requireContext(), R.color.signal_colorPrimary));
binding.profileDescriptionText.setOnLinkClickListener(v -> CommunicationActions.openBrowserLink(requireContext(), getString(R.string.EditProfileFragment__support_link)));
if (FeatureFlags.phoneNumberPrivacy()) {
binding.whoCanFindMeContainer.setVisibility(View.VISIBLE);
binding.whoCanFindMeContainer.setOnClickListener(v -> SafeNavigation.safeNavigate(Navigation.findNavController(v), EditProfileFragmentDirections.actionCreateProfileFragmentToPhoneNumberPrivacy()));
// TODO [alex] -- Where does this value come from?
binding.whoCanFindMeDescription.setText(R.string.PhoneNumberPrivacy_everyone);
}
}
this.finishButton.setOnClickListener(v -> {
this.finishButton.setSpinning();
binding.finishButton.setOnClickListener(v -> {
binding.finishButton.setSpinning();
handleUpload();
});
this.finishButton.setText(arguments.getInt(NEXT_BUTTON_TEXT, R.string.CreateProfileActivity_next));
binding.finishButton.setText(arguments.getInt(NEXT_BUTTON_TEXT, R.string.CreateProfileActivity_next));
if (arguments.getBoolean(SHOW_TOOLBAR, true)) {
this.toolbar.setVisibility(View.VISIBLE);
this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
this.title.setVisibility(View.GONE);
binding.toolbar.setVisibility(View.VISIBLE);
binding.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
binding.title.setVisibility(View.GONE);
}
}
private void initializeProfileName() {
viewModel.isFormValid().observe(getViewLifecycleOwner(), isValid -> {
finishButton.setEnabled(isValid);
finishButton.setAlpha(isValid ? 1f : 0.5f);
binding.finishButton.setEnabled(isValid);
binding.finishButton.setAlpha(isValid ? 1f : 0.5f);
});
viewModel.givenName().observe(getViewLifecycleOwner(), givenName -> updateFieldIfNeeded(this.givenName, givenName));
viewModel.givenName().observe(getViewLifecycleOwner(), givenName -> updateFieldIfNeeded(binding.givenName, givenName));
viewModel.familyName().observe(getViewLifecycleOwner(), familyName -> updateFieldIfNeeded(this.familyName, familyName));
viewModel.familyName().observe(getViewLifecycleOwner(), familyName -> updateFieldIfNeeded(binding.familyName, familyName));
viewModel.profileName().observe(getViewLifecycleOwner(), profileName -> preview.setText(profileName.toString()));
viewModel.profileName().observe(getViewLifecycleOwner(), profileName -> binding.namePreview.setText(profileName.toString()));
}
private void initializeProfileAvatar() {
viewModel.avatar().observe(getViewLifecycleOwner(), bytes -> {
if (bytes == null) {
GlideApp.with(this).clear(avatar);
GlideApp.with(this).clear(binding.avatar);
return;
}
GlideApp.with(this)
.load(bytes)
.circleCrop()
.into(avatar);
.into(binding.avatar);
});
viewModel.avatarColor().observe(getViewLifecycleOwner(), avatarColor -> {
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(avatarColor);
avatarPreview.getDrawable().setColorFilter(new SimpleColorFilter(foregroundColor.getColorInt()));
avatarPreviewBackground.getDrawable().setColorFilter(new SimpleColorFilter(avatarColor.colorInt()));
binding.avatarPlaceholder.getDrawable().setColorFilter(new SimpleColorFilter(foregroundColor.getColorInt()));
binding.avatarBackground.getDrawable().setColorFilter(new SimpleColorFilter(avatarColor.colorInt()));
});
}
@ -312,7 +303,7 @@ public class EditProfileFragment extends LoggingFragment {
}
private void handleFinishedLegacy() {
finishButton.cancelSpinning();
binding.finishButton.cancelSpinning();
if (nextIntent != null) startActivity(nextIntent);
controller.onProfileNameUploadCompleted();
@ -323,16 +314,16 @@ public class EditProfileFragment extends LoggingFragment {
int[] finishButtonLocation = new int[2];
int[] revealLocation = new int[2];
finishButton.getLocationInWindow(finishButtonLocation);
reveal.getLocationInWindow(revealLocation);
binding.finishButton.getLocationInWindow(finishButtonLocation);
binding.reveal.getLocationInWindow(revealLocation);
int finishX = finishButtonLocation[0] - revealLocation[0];
int finishY = finishButtonLocation[1] - revealLocation[1];
finishX += finishButton.getWidth() / 2;
finishY += finishButton.getHeight() / 2;
finishX += binding.finishButton.getWidth() / 2;
finishY += binding.finishButton.getHeight() / 2;
Animator animation = ViewAnimationUtils.createCircularReveal(reveal, finishX, finishY, 0f, (float) Math.max(reveal.getWidth(), reveal.getHeight()));
Animator animation = ViewAnimationUtils.createCircularReveal(binding.reveal, finishX, finishY, 0f, (float) Math.max(binding.reveal.getWidth(), binding.reveal.getHeight()));
animation.setDuration(500);
animation.addListener(new Animator.AnimatorListener() {
@Override
@ -340,7 +331,7 @@ public class EditProfileFragment extends LoggingFragment {
@Override
public void onAnimationEnd(Animator animation) {
finishButton.cancelSpinning();
binding.finishButton.cancelSpinning();
if (nextIntent != null && getActivity() != null) {
startActivity(nextIntent);
}
@ -355,7 +346,7 @@ public class EditProfileFragment extends LoggingFragment {
public void onAnimationRepeat(Animator animation) {}
});
reveal.setVisibility(View.VISIBLE);
binding.reveal.setVisibility(View.VISIBLE);
animation.start();
}

Wyświetl plik

@ -0,0 +1,46 @@
package org.thoughtcrime.securesms.profiles.edit.pnp
import androidx.fragment.app.viewModels
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.LifecycleDisposable
/**
* Allows the user to select who can see their phone number during registration.
*/
class WhoCanSeeMyPhoneNumberFragment : DSLSettingsFragment(titleId = R.string.WhoCanSeeMyPhoneNumberFragment__who_can_find_me_by_number) {
private val viewModel: WhoCanSeeMyPhoneNumberViewModel by viewModels()
private val lifecycleDisposable = LifecycleDisposable()
override fun bindAdapter(adapter: DSLSettingsAdapter) {
require(FeatureFlags.phoneNumberPrivacy())
lifecycleDisposable += viewModel.state.subscribe {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
private fun getConfiguration(state: WhoCanSeeMyPhoneNumberState): DSLConfiguration {
return configure {
radioPref(
title = DSLSettingsText.from(R.string.PhoneNumberPrivacy_everyone),
summary = DSLSettingsText.from(R.string.WhoCanSeeMyPhoneNumberFragment__anyone_who_has),
isChecked = state == WhoCanSeeMyPhoneNumberState.EVERYONE,
onClick = { viewModel.onEveryoneCanSeeMyPhoneNumberSelected() }
)
radioPref(
title = DSLSettingsText.from(R.string.PhoneNumberPrivacy_nobody),
summary = DSLSettingsText.from(R.string.WhoCanSeeMyPhoneNumberFragment__nobody_on_signal),
isChecked = state == WhoCanSeeMyPhoneNumberState.NOBODY,
onClick = { viewModel.onNobodyCanSeeMyPhoneNumberSelected() }
)
}
}
}

Wyświetl plik

@ -0,0 +1,6 @@
package org.thoughtcrime.securesms.profiles.edit.pnp
enum class WhoCanSeeMyPhoneNumberState {
EVERYONE,
NOBODY
}

Wyświetl plik

@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.profiles.edit.pnp
import androidx.lifecycle.ViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.thoughtcrime.securesms.util.rx.RxStore
class WhoCanSeeMyPhoneNumberViewModel : ViewModel() {
private val store = RxStore(WhoCanSeeMyPhoneNumberState.EVERYONE)
private val disposables = CompositeDisposable()
val state: Flowable<WhoCanSeeMyPhoneNumberState> = store.stateFlowable.subscribeOn(AndroidSchedulers.mainThread())
fun onEveryoneCanSeeMyPhoneNumberSelected() {
store.update { WhoCanSeeMyPhoneNumberState.EVERYONE }
}
fun onNobodyCanSeeMyPhoneNumberSelected() {
store.update { WhoCanSeeMyPhoneNumberState.NOBODY }
}
override fun onCleared() {
disposables.clear()
}
}

Wyświetl plik

@ -0,0 +1,32 @@
package org.thoughtcrime.securesms.profiles.manage
import org.thoughtcrime.securesms.databinding.CopyButtonBinding
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
/**
* Outlined button that allows the user to copy a piece of data.
*/
object CopyButton {
fun register(mappingAdapter: MappingAdapter) {
mappingAdapter.registerFactory(Model::class.java, BindingFactory(::ViewHolder, CopyButtonBinding::inflate))
}
class Model(
val text: CharSequence,
val onClick: (Model) -> Unit
) : MappingModel<Model> {
override fun areItemsTheSame(newItem: Model): Boolean = true
override fun areContentsTheSame(newItem: Model): Boolean = text == newItem.text
}
private class ViewHolder(binding: CopyButtonBinding) : BindingViewHolder<Model, CopyButtonBinding>(binding) {
override fun bind(model: Model) {
binding.root.text = model.text
binding.root.setOnClickListener { model.onClick(model) }
}
}
}

Wyświetl plik

@ -7,33 +7,31 @@ import android.util.TypedValue;
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.core.content.res.ResourcesCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModelProviders;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import com.airbnb.lottie.SimpleColorFilter;
import com.bumptech.glide.Glide;
import com.google.android.material.snackbar.Snackbar;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.AvatarPreviewActivity;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment;
import org.thoughtcrime.securesms.badges.BadgeImageView;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.badges.self.none.BecomeASustainerFragment;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.components.emoji.EmojiUtil;
import org.thoughtcrime.securesms.databinding.ManageProfileFragmentBinding;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarState;
@ -49,64 +47,42 @@ import java.util.Optional;
public class ManageProfileFragment extends LoggingFragment {
private static final String TAG = Log.tag(ManageProfileFragment.class);
private Toolbar toolbar;
private ImageView avatarView;
private ImageView avatarPlaceholderView;
private TextView profileNameView;
private View profileNameContainer;
private TextView usernameView;
private View usernameContainer;
private TextView aboutView;
private View aboutContainer;
private ImageView aboutEmojiView;
private AlertDialog avatarProgress;
private TextView avatarInitials;
private ImageView avatarBackground;
private View badgesContainer;
private BadgeImageView badgeView;
private ManageProfileViewModel viewModel;
private AlertDialog avatarProgress;
private ManageProfileViewModel viewModel;
private ManageProfileFragmentBinding binding;
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.manage_profile_fragment, container, false);
binding = ManageProfileFragmentBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@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);
this.aboutEmojiView = view.findViewById(R.id.manage_profile_about_icon);
this.avatarInitials = view.findViewById(R.id.manage_profile_avatar_initials);
this.avatarBackground = view.findViewById(R.id.manage_profile_avatar_background);
this.badgesContainer = view.findViewById(R.id.manage_profile_badges_container);
this.badgeView = view.findViewById(R.id.manage_profile_badge);
new UsernameEditFragment.ResultContract().registerForResult(getParentFragmentManager(), getViewLifecycleOwner(), isUsernameCreated -> {
Snackbar.make(view, R.string.ManageProfileFragment__username_created, Snackbar.LENGTH_SHORT).show();
});
UsernameShareBottomSheet.ResultContract.INSTANCE.registerForResult(getParentFragmentManager(), getViewLifecycleOwner(), isCopiedToClipboard -> {
Snackbar.make(view, R.string.ManageProfileFragment__username_copied, Snackbar.LENGTH_SHORT).show();
});
initializeViewModel();
this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
binding.toolbar.setNavigationOnClickListener(v -> requireActivity().finish());
View editAvatar = view.findViewById(R.id.manage_profile_edit_photo);
editAvatar.setOnClickListener(v -> onEditAvatarClicked());
binding.manageProfileEditPhoto.setOnClickListener(v -> onEditAvatarClicked());
this.profileNameContainer.setOnClickListener(v -> {
binding.manageProfileNameContainer.setOnClickListener(v -> {
SafeNavigation.safeNavigate(Navigation.findNavController(v), ManageProfileFragmentDirections.actionManageProfileName());
});
this.usernameContainer.setOnClickListener(v -> {
binding.manageProfileUsernameContainer.setOnClickListener(v -> {
SafeNavigation.safeNavigate(Navigation.findNavController(v), ManageProfileFragmentDirections.actionManageUsername());
});
this.aboutContainer.setOnClickListener(v -> {
binding.manageProfileAboutContainer.setOnClickListener(v -> {
SafeNavigation.safeNavigate(Navigation.findNavController(v), ManageProfileFragmentDirections.actionManageAbout());
});
@ -119,6 +95,7 @@ public class ManageProfileFragment extends LoggingFragment {
}
});
EmojiTextView avatarInitials = binding.manageProfileAvatarInitials;
avatarInitials.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (avatarInitials.length() > 0) {
updateInitials(avatarInitials.getText().toString());
@ -126,7 +103,7 @@ public class ManageProfileFragment extends LoggingFragment {
});
if (FeatureFlags.donorBadges()) {
badgesContainer.setOnClickListener(v -> {
binding.manageProfileBadgesContainer.setOnClickListener(v -> {
if (Recipient.self().getBadges().isEmpty()) {
BecomeASustainerFragment.show(getParentFragmentManager());
} else {
@ -134,17 +111,27 @@ public class ManageProfileFragment extends LoggingFragment {
}
});
} else {
badgesContainer.setVisibility(View.GONE);
binding.manageProfileBadgesContainer.setVisibility(View.GONE);
}
avatarView.setOnClickListener(v -> {
binding.manageProfileAvatar.setOnClickListener(v -> {
startActivity(AvatarPreviewActivity.intentFromRecipientId(requireContext(), Recipient.self().getId()),
AvatarPreviewActivity.createTransitionBundle(requireActivity(), avatarView));
AvatarPreviewActivity.createTransitionBundle(requireActivity(), binding.manageProfileAvatar));
});
binding.manageProfileUsernameShare.setOnClickListener(v -> {
SafeNavigation.safeNavigate(Navigation.findNavController(v), ManageProfileFragmentDirections.actionManageProfileFragmentToShareUsernameDialog());
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
private void initializeViewModel() {
viewModel = ViewModelProviders.of(this, new ManageProfileViewModel.Factory()).get(ManageProfileViewModel.class);
viewModel = new ViewModelProvider(this, new ManageProfileViewModel.Factory()).get(ManageProfileViewModel.class);
LiveData<Optional<byte[]>> avatarImage = Transformations.map(LiveDataUtil.distinctUntilChanged(viewModel.getAvatar(), (b1, b2) -> Arrays.equals(b1.getAvatar(), b2.getAvatar())),
b -> Optional.ofNullable(b.getAvatar()));
@ -160,7 +147,7 @@ public class ManageProfileFragment extends LoggingFragment {
if (viewModel.shouldShowUsername()) {
viewModel.getUsername().observe(getViewLifecycleOwner(), this::presentUsername);
} else {
usernameContainer.setVisibility(View.GONE);
binding.manageProfileUsernameContainer.setVisibility(View.GONE);
}
}
@ -169,9 +156,9 @@ public class ManageProfileFragment extends LoggingFragment {
Glide.with(this)
.load(avatarData.get())
.circleCrop()
.into(avatarView);
.into(binding.manageProfileAvatar);
} else {
Glide.with(this).load((Drawable) null).into(avatarView);
Glide.with(this).load((Drawable) null).into(binding.manageProfileAvatar);
}
}
@ -180,21 +167,21 @@ public class ManageProfileFragment extends LoggingFragment {
CharSequence initials = NameUtil.getAbbreviation(avatarState.getSelf().getDisplayName(requireContext()));
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(avatarState.getSelf().getAvatarColor());
avatarBackground.setColorFilter(new SimpleColorFilter(avatarState.getSelf().getAvatarColor().colorInt()));
avatarPlaceholderView.setColorFilter(new SimpleColorFilter(foregroundColor.getColorInt()));
avatarInitials.setTextColor(foregroundColor.getColorInt());
binding.manageProfileAvatarBackground.setColorFilter(new SimpleColorFilter(avatarState.getSelf().getAvatarColor().colorInt()));
binding.manageProfileAvatarPlaceholder.setColorFilter(new SimpleColorFilter(foregroundColor.getColorInt()));
binding.manageProfileAvatarInitials.setTextColor(foregroundColor.getColorInt());
if (TextUtils.isEmpty(initials)) {
avatarPlaceholderView.setVisibility(View.VISIBLE);
avatarInitials.setVisibility(View.GONE);
binding.manageProfileAvatarPlaceholder.setVisibility(View.VISIBLE);
binding.manageProfileAvatarInitials.setVisibility(View.GONE);
} else {
updateInitials(initials.toString());
avatarPlaceholderView.setVisibility(View.GONE);
avatarInitials.setVisibility(View.VISIBLE);
binding.manageProfileAvatarPlaceholder.setVisibility(View.GONE);
binding.manageProfileAvatarInitials.setVisibility(View.VISIBLE);
}
} else {
avatarPlaceholderView.setVisibility(View.GONE);
avatarInitials.setVisibility(View.GONE);
binding.manageProfileAvatarPlaceholder.setVisibility(View.GONE);
binding.manageProfileAvatarInitials.setVisibility(View.GONE);
}
if (avatarProgress == null && avatarState.getLoadingState() == ManageProfileViewModel.LoadingState.LOADING) {
@ -205,53 +192,59 @@ public class ManageProfileFragment extends LoggingFragment {
}
private void updateInitials(String initials) {
avatarInitials.setTextSize(TypedValue.COMPLEX_UNIT_PX, Avatars.getTextSizeForLength(requireContext(), initials, avatarInitials.getMeasuredWidth() * 0.8f, avatarInitials.getMeasuredWidth() * 0.45f));
avatarInitials.setText(initials);
binding.manageProfileAvatarInitials.setTextSize(TypedValue.COMPLEX_UNIT_PX,
Avatars.getTextSizeForLength(requireContext(),
initials,
binding.manageProfileAvatarInitials.getMeasuredWidth() * 0.8f,
binding.manageProfileAvatarInitials.getMeasuredWidth() * 0.45f));
binding.manageProfileAvatarInitials.setText(initials);
}
private void presentProfileName(@Nullable ProfileName profileName) {
if (profileName == null || profileName.isEmpty()) {
profileNameView.setText(R.string.ManageProfileFragment_profile_name);
binding.manageProfileName.setText(R.string.ManageProfileFragment_profile_name);
} else {
profileNameView.setText(profileName.toString());
binding.manageProfileName.setText(profileName.toString());
}
}
private void presentUsername(@Nullable String username) {
if (username == null || username.isEmpty()) {
usernameView.setText(R.string.ManageProfileFragment_username);
binding.manageProfileUsername.setText(R.string.ManageProfileFragment_username);
binding.manageProfileUsernameShare.setVisibility(View.GONE);
} else {
usernameView.setText(username);
binding.manageProfileUsername.setText(username);
binding.manageProfileUsernameShare.setVisibility(View.VISIBLE);
}
}
private void presentAbout(@Nullable String about) {
if (about == null || about.isEmpty()) {
aboutView.setText(R.string.ManageProfileFragment_about);
binding.manageProfileAbout.setText(R.string.ManageProfileFragment_about);
} else {
aboutView.setText(about);
binding.manageProfileAbout.setText(about);
}
}
private void presentAboutEmoji(@NonNull String aboutEmoji) {
if (aboutEmoji == null || aboutEmoji.isEmpty()) {
aboutEmojiView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
binding.manageProfileAboutIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
} else {
Drawable emoji = EmojiUtil.convertToDrawable(requireContext(), aboutEmoji);
if (emoji != null) {
aboutEmojiView.setImageDrawable(emoji);
binding.manageProfileAboutIcon.setImageDrawable(emoji);
} else {
aboutEmojiView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
binding.manageProfileAboutIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
}
}
}
private void presentBadge(@NonNull Optional<Badge> badge) {
if (badge.isPresent() && badge.get().getVisible() && !badge.get().isExpired()) {
badgeView.setBadge(badge.orElse(null));
binding.manageProfileBadge.setBadge(badge.orElse(null));
} else {
badgeView.setBadge(null);
binding.manageProfileBadge.setBadge(null);
}
}

Wyświetl plik

@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.profiles.manage
import org.thoughtcrime.securesms.databinding.ShareButtonBinding
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
object ShareButton {
fun register(adapter: MappingAdapter) {
adapter.registerFactory(Model::class.java, BindingFactory(::ViewHolder, ShareButtonBinding::inflate))
}
class Model(
val text: CharSequence,
val onClick: (Model) -> Unit
) : MappingModel<Model> {
override fun areItemsTheSame(newItem: Model): Boolean = true
override fun areContentsTheSame(newItem: Model): Boolean = text == newItem.text
}
private class ViewHolder(binding: ShareButtonBinding) : BindingViewHolder<Model, ShareButtonBinding>(binding) {
override fun bind(model: Model) {
binding.shareButton.setOnClickListener { model.onClick(model) }
}
}
}

Wyświetl plik

@ -1,38 +1,56 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProviders;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import org.signal.core.util.DimensionUnit;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher;
import org.thoughtcrime.securesms.databinding.UsernameEditFragmentBinding;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.FragmentResultContract;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
import java.util.Objects;
import java.util.function.Consumer;
public class UsernameEditFragment extends LoggingFragment {
private static final float DISABLED_ALPHA = 0.5f;
private UsernameEditViewModel viewModel;
private EditText usernameInput;
private TextView usernameSubtext;
private CircularProgressMaterialButton submitButton;
private CircularProgressMaterialButton deleteButton;
private UsernameEditViewModel viewModel;
private UsernameEditFragmentBinding binding;
private ImageView suffixProgress;
private LifecycleDisposable lifecycleDisposable;
public static UsernameEditFragment newInstance() {
return new UsernameEditFragment();
@ -40,46 +58,97 @@ public class UsernameEditFragment extends LoggingFragment {
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.username_edit_fragment, container, false);
binding = UsernameEditFragmentBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
usernameInput = view.findViewById(R.id.username_text);
usernameSubtext = view.findViewById(R.id.username_subtext);
submitButton = view.findViewById(R.id.username_submit_button);
deleteButton = view.findViewById(R.id.username_delete_button);
binding.toolbar.setNavigationOnClickListener(v -> Navigation.findNavController(view).popBackStack());
view.<Toolbar>findViewById(R.id.toolbar)
.setNavigationOnClickListener(v -> Navigation.findNavController(view)
.popBackStack());
binding.usernameTextWrapper.setErrorIconDrawable(null);
viewModel = ViewModelProviders.of(this, new UsernameEditViewModel.Factory()).get(UsernameEditViewModel.class);
lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(getViewLifecycleOwner());
viewModel.getUiState().observe(getViewLifecycleOwner(), this::onUiStateChanged);
viewModel = new ViewModelProvider(this, new UsernameEditViewModel.Factory()).get(UsernameEditViewModel.class);
lifecycleDisposable.add(viewModel.getUiState().subscribe(this::onUiStateChanged));
viewModel.getEvents().observe(getViewLifecycleOwner(), this::onEvent);
submitButton.setOnClickListener(v -> viewModel.onUsernameSubmitted(usernameInput.getText().toString()));
deleteButton.setOnClickListener(v -> viewModel.onUsernameDeleted());
binding.usernameSubmitButton.setOnClickListener(v -> viewModel.onUsernameSubmitted(binding.usernameText.getText().toString()));
binding.usernameDeleteButton.setOnClickListener(v -> viewModel.onUsernameDeleted());
usernameInput.setText(Recipient.self().getUsername().orElse(null));
usernameInput.addTextChangedListener(new SimpleTextWatcher() {
binding.usernameText.setText(Recipient.self().getUsername().orElse(null));
binding.usernameText.addTextChangedListener(new SimpleTextWatcher() {
@Override
public void onTextChanged(String text) {
viewModel.onUsernameUpdated(text);
}
});
usernameInput.setOnEditorActionListener((v, actionId, event) -> {
binding.usernameText.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
viewModel.onUsernameSubmitted(usernameInput.getText().toString());
viewModel.onUsernameSubmitted(binding.usernameText.getText().toString());
return true;
}
return false;
});
binding.usernameDescription.setLinkColor(ContextCompat.getColor(requireContext(), R.color.signal_colorPrimary));
binding.usernameDescription.setLearnMoreVisible(true);
binding.usernameDescription.setOnLinkClickListener(this::onLearnMore);
initializeSuffix();
ViewUtil.focusAndShowKeyboard(binding.usernameText);
}
private void initializeSuffix() {
TextView suffixTextView = binding.usernameTextWrapper.getSuffixTextView();
Drawable pipe = Objects.requireNonNull(ContextCompat.getDrawable(requireContext(), R.drawable.pipe_divider));
pipe.setBounds(0, 0, (int) DimensionUnit.DP.toPixels(1f), (int) DimensionUnit.DP.toPixels(20f));
suffixTextView.setCompoundDrawablesRelative(pipe, null, null, null);
LinearLayout suffixParent = (LinearLayout) suffixTextView.getParent();
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
ViewUtil.setLeftMargin(suffixTextView, (int) DimensionUnit.DP.toPixels(16f));
binding.usernameTextWrapper.getSuffixTextView().setCompoundDrawablePadding((int) DimensionUnit.DP.toPixels(16f));
layoutParams.topMargin = suffixTextView.getPaddingTop();
layoutParams.bottomMargin = suffixTextView.getPaddingBottom();
suffixProgress = new ImageView(requireContext());
suffixProgress.setImageDrawable(UsernameSuffix.getInProgressDrawable(requireContext()));
suffixParent.addView(suffixProgress, 0, layoutParams);
suffixTextView.setOnClickListener(this::onLearnMore);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
suffixProgress = null;
}
private void onLearnMore(@Nullable View unused) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(new StringBuilder("#\n").append(getString(R.string.UsernameEditFragment__what_is_this_number)))
.setMessage(R.string.UsernameEditFragment__these_digits_help_keep)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {})
.show();
}
private void onUiStateChanged(@NonNull UsernameEditViewModel.State state) {
EditText usernameInput = binding.usernameText;
CircularProgressMaterialButton submitButton = binding.usernameSubmitButton;
CircularProgressMaterialButton deleteButton = binding.usernameDeleteButton;
TextInputLayout usernameInputWrapper = binding.usernameTextWrapper;
usernameInput.setEnabled(true);
presentSuffix(state.getUsernameSuffix());
switch (state.getButtonState()) {
case SUBMIT:
@ -128,39 +197,57 @@ public class UsernameEditFragment extends LoggingFragment {
switch (state.getUsernameStatus()) {
case NONE:
usernameSubtext.setText("");
usernameInputWrapper.setError(null);
break;
case TOO_SHORT:
case TOO_LONG:
usernameSubtext.setText(getResources().getString(R.string.UsernameEditFragment_usernames_must_be_between_a_and_b_characters, UsernameUtil.MIN_LENGTH, UsernameUtil.MAX_LENGTH));
usernameSubtext.setTextColor(getResources().getColor(R.color.core_red));
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_must_be_between_a_and_b_characters, UsernameUtil.MIN_LENGTH, UsernameUtil.MAX_LENGTH));
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
break;
case INVALID_CHARACTERS:
usernameSubtext.setText(R.string.UsernameEditFragment_usernames_can_only_include);
usernameSubtext.setTextColor(getResources().getColor(R.color.core_red));
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_can_only_include));
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
break;
case CANNOT_START_WITH_NUMBER:
usernameSubtext.setText(R.string.UsernameEditFragment_usernames_cannot_begin_with_a_number);
usernameSubtext.setTextColor(getResources().getColor(R.color.core_red));
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_usernames_cannot_begin_with_a_number));
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
break;
case INVALID_GENERIC:
usernameSubtext.setText(R.string.UsernameEditFragment_username_is_invalid);
usernameSubtext.setTextColor(getResources().getColor(R.color.core_red));
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_username_is_invalid));
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
break;
case TAKEN:
usernameSubtext.setText(R.string.UsernameEditFragment_this_username_is_taken);
usernameSubtext.setTextColor(getResources().getColor(R.color.core_red));
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_this_username_is_taken));
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_colorError)));
break;
case AVAILABLE:
usernameSubtext.setText(R.string.UsernameEditFragment_this_username_is_available);
usernameSubtext.setTextColor(getResources().getColor(R.color.core_green));
usernameInputWrapper.setError(getResources().getString(R.string.UsernameEditFragment_this_username_is_available));
usernameInputWrapper.setErrorTextColor(ColorStateList.valueOf(getResources().getColor(R.color.signal_accent_green)));
break;
}
}
private void presentSuffix(@NonNull UsernameSuffix usernameSuffix) {
binding.usernameTextWrapper.setSuffixText(usernameSuffix.getCharSequence());
boolean isInProgress = usernameSuffix.isInProgress();
if (isInProgress) {
suffixProgress.setVisibility(View.VISIBLE);
} else {
suffixProgress.setVisibility(View.GONE);
}
}
private void onEvent(@NonNull UsernameEditViewModel.Event event) {
switch (event) {
case SUBMIT_SUCCESS:
ResultContract.setUsernameCreated(getParentFragmentManager());
Toast.makeText(requireContext(), R.string.UsernameEditFragment_successfully_set_username, Toast.LENGTH_SHORT).show();
NavHostFragment.findNavController(this).popBackStack();
break;
@ -179,4 +266,23 @@ public class UsernameEditFragment extends LoggingFragment {
break;
}
}
static class ResultContract extends FragmentResultContract<Boolean> {
private static final String REQUEST_KEY = "username_created";
protected ResultContract() {
super(REQUEST_KEY);
}
static void setUsernameCreated(@NonNull FragmentManager fragmentManager) {
Bundle bundle = new Bundle();
bundle.putBoolean(REQUEST_KEY, true);
fragmentManager.setFragmentResult(REQUEST_KEY, bundle);
}
@Override
protected Boolean getResult(@NonNull Bundle bundle) {
return bundle.getBoolean(REQUEST_KEY, false);
}
}
}

Wyświetl plik

@ -5,7 +5,6 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@ -16,81 +15,80 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.UsernameUtil.InvalidReason;
import org.thoughtcrime.securesms.util.rx.RxStore;
import java.util.Optional;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers;
class UsernameEditViewModel extends ViewModel {
private static final String TAG = Log.tag(UsernameEditViewModel.class);
private final Application application;
private final MutableLiveData<State> uiState;
private final SingleLiveEvent<Event> events;
private final UsernameEditRepository repo;
private final RxStore<State> uiState;
private UsernameEditViewModel() {
this.application = ApplicationDependencies.getApplication();
this.repo = new UsernameEditRepository();
this.uiState = new MutableLiveData<>();
this.uiState = new RxStore<>(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, UsernameSuffix.NONE), Schedulers.computation());
this.events = new SingleLiveEvent<>();
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE));
}
void onUsernameUpdated(@NonNull String username) {
if (TextUtils.isEmpty(username) && Recipient.self().getUsername().isPresent()) {
uiState.setValue(new State(ButtonState.DELETE, UsernameStatus.NONE));
return;
}
uiState.update(state -> {
if (TextUtils.isEmpty(username) && Recipient.self().getUsername().isPresent()) {
return new State(ButtonState.DELETE, UsernameStatus.NONE, state.usernameSuffix);
}
if (username.equals(Recipient.self().getUsername().orElse(null))) {
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE));
return;
}
if (username.equals(Recipient.self().getUsername().orElse(null))) {
return new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, state.usernameSuffix);
}
Optional<InvalidReason> invalidReason = UsernameUtil.checkUsername(username);
Optional<InvalidReason> invalidReason = UsernameUtil.checkUsername(username);
if (invalidReason.isPresent()) {
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, mapUsernameError(invalidReason.get())));
return;
}
uiState.setValue(new State(ButtonState.SUBMIT, UsernameStatus.NONE));
return invalidReason.map(reason -> new State(ButtonState.SUBMIT_DISABLED, mapUsernameError(reason), state.usernameSuffix))
.orElseGet(() -> new State(ButtonState.SUBMIT, UsernameStatus.NONE, state.usernameSuffix));
});
}
void onUsernameSubmitted(@NonNull String username) {
if (username.equals(Recipient.self().getUsername().orElse(null))) {
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, state.usernameSuffix));
return;
}
Optional<InvalidReason> invalidReason = UsernameUtil.checkUsername(username);
if (invalidReason.isPresent()) {
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, mapUsernameError(invalidReason.get())));
uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, mapUsernameError(invalidReason.get()), state.usernameSuffix));
return;
}
uiState.setValue(new State(ButtonState.SUBMIT_LOADING, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.SUBMIT_LOADING, UsernameStatus.NONE, state.usernameSuffix));
repo.setUsername(username, (result) -> {
ThreadUtil.runOnMain(() -> {
switch (result) {
case SUCCESS:
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.NONE, state.usernameSuffix));
events.postValue(Event.SUBMIT_SUCCESS);
break;
case USERNAME_INVALID:
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.INVALID_GENERIC));
uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.INVALID_GENERIC, state.usernameSuffix));
events.postValue(Event.SUBMIT_FAIL_INVALID);
break;
case USERNAME_UNAVAILABLE:
uiState.setValue(new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.TAKEN));
uiState.update(state -> new State(ButtonState.SUBMIT_DISABLED, UsernameStatus.TAKEN, state.usernameSuffix));
events.postValue(Event.SUBMIT_FAIL_TAKEN);
break;
case NETWORK_ERROR:
uiState.setValue(new State(ButtonState.SUBMIT, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.SUBMIT, UsernameStatus.NONE, state.usernameSuffix));
events.postValue(Event.NETWORK_FAILURE);
break;
}
@ -99,17 +97,17 @@ class UsernameEditViewModel extends ViewModel {
}
void onUsernameDeleted() {
uiState.setValue(new State(ButtonState.DELETE_LOADING, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.DELETE_LOADING, UsernameStatus.NONE, state.usernameSuffix));
repo.deleteUsername((result) -> {
ThreadUtil.runOnMain(() -> {
switch (result) {
case SUCCESS:
uiState.postValue(new State(ButtonState.DELETE_DISABLED, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.DELETE_DISABLED, UsernameStatus.NONE, state.usernameSuffix));
events.postValue(Event.DELETE_SUCCESS);
break;
case NETWORK_ERROR:
uiState.postValue(new State(ButtonState.DELETE, UsernameStatus.NONE));
uiState.update(state -> new State(ButtonState.DELETE, UsernameStatus.NONE, state.usernameSuffix));
events.postValue(Event.NETWORK_FAILURE);
break;
}
@ -117,8 +115,8 @@ class UsernameEditViewModel extends ViewModel {
});
}
@NonNull LiveData<State> getUiState() {
return uiState;
@NonNull Flowable<State> getUiState() {
return uiState.getStateFlowable().observeOn(AndroidSchedulers.mainThread());
}
@NonNull LiveData<Event> getEvents() {
@ -138,12 +136,15 @@ class UsernameEditViewModel extends ViewModel {
static class State {
private final ButtonState buttonState;
private final UsernameStatus usernameStatus;
private final UsernameSuffix usernameSuffix;
private State(@NonNull ButtonState buttonState,
@NonNull UsernameStatus usernameStatus)
@NonNull UsernameStatus usernameStatus,
@NonNull UsernameSuffix usernameSuffix)
{
this.buttonState = buttonState;
this.usernameStatus = usernameStatus;
this.usernameSuffix = usernameSuffix;
}
@NonNull ButtonState getButtonState() {
@ -153,6 +154,10 @@ class UsernameEditViewModel extends ViewModel {
@NonNull UsernameStatus getUsernameStatus() {
return usernameStatus;
}
@NonNull UsernameSuffix getUsernameSuffix() {
return usernameSuffix;
}
}
enum UsernameStatus {

Wyświetl plik

@ -0,0 +1,120 @@
package org.thoughtcrime.securesms.profiles.manage
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.core.app.ShareCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.setFragmentResult
import androidx.navigation.fragment.findNavController
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.FragmentResultContract
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.Util
/**
* Allows the user to either share their username directly or to copy it to their clipboard.
*/
class UsernameShareBottomSheet : DSLSettingsBottomSheetFragment() {
companion object {
private const val REQUEST_KEY = "copy_username"
}
private val lifecycleDisposable = LifecycleDisposable()
override fun bindAdapter(adapter: DSLSettingsAdapter) {
CopyButton.register(adapter)
ShareButton.register(adapter)
lifecycleDisposable += Recipient.observable(Recipient.self().id).subscribe {
adapter.submitList(getConfiguration(it).toMappingModelList())
}
}
private fun getConfiguration(recipient: Recipient): DSLConfiguration {
return configure {
noPadTextPref(
title = DSLSettingsText.from(
R.string.UsernameShareBottomSheet__copy_or_share_a_username_link,
DSLSettingsText.TextAppearanceModifier(R.style.Signal_Text_BodyMedium),
DSLSettingsText.CenterModifier,
DSLSettingsText.ColorModifier(
ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant),
)
)
)
space(DimensionUnit.DP.toPixels(32f).toInt())
val username = recipient.username.get()
customPref(
CopyButton.Model(
text = username,
onClick = {
copyToClipboard(it)
}
)
)
space(DimensionUnit.DP.toPixels(20f).toInt())
customPref(
CopyButton.Model(
text = getString(R.string.signal_me_url, username),
onClick = {
copyToClipboard(it)
}
)
)
space(DimensionUnit.DP.toPixels(24f).toInt())
customPref(
ShareButton.Model(
text = getString(R.string.signal_me_url, username),
onClick = {
openShareSheet(it.text)
}
)
)
space(DimensionUnit.DP.toPixels(18f).toInt())
}
}
private fun copyToClipboard(model: CopyButton.Model) {
Util.copyToClipboard(requireContext(), model.text)
setFragmentResult(REQUEST_KEY, Bundle().apply { putBoolean(REQUEST_KEY, true) })
findNavController().popBackStack()
}
private fun openShareSheet(charSequence: CharSequence) {
val mimeType = Intent.normalizeMimeType("text/plain")
val shareIntent = ShareCompat.IntentBuilder(requireContext())
.setText(charSequence)
.setType(mimeType)
.createChooserIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
startActivity(shareIntent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(requireContext(), R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show()
}
}
object ResultContract : FragmentResultContract<Boolean>(REQUEST_KEY) {
override fun getResult(bundle: Bundle): Boolean {
return bundle.getBoolean(REQUEST_KEY, false)
}
}
}

Wyświetl plik

@ -0,0 +1,43 @@
package org.thoughtcrime.securesms.profiles.manage
import android.content.Context
import androidx.core.content.ContextCompat
import com.google.android.material.progressindicator.CircularProgressIndicatorSpec
import com.google.android.material.progressindicator.IndeterminateDrawable
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
/**
* Describes the state of the username suffix, which is a spanned CharSequence.
*/
data class UsernameSuffix(
val charSequence: CharSequence?
) {
val isInProgress = charSequence == null
companion object {
@JvmField
val LOADING = UsernameSuffix(null)
@JvmField
val NONE = UsernameSuffix("")
@JvmStatic
fun fromCode(code: Int) = UsernameSuffix("#$code")
@JvmStatic
fun getInProgressDrawable(context: Context): IndeterminateDrawable<CircularProgressIndicatorSpec> {
val progressIndicatorSpec = CircularProgressIndicatorSpec(context, null).apply {
indicatorInset = 0
indicatorSize = DimensionUnit.DP.toPixels(16f).toInt()
trackColor = ContextCompat.getColor(context, R.color.signal_colorOnSurfaceVariant)
trackThickness = DimensionUnit.DP.toPixels(1f).toInt()
}
return IndeterminateDrawable.createCircularDrawable(context, progressIndicatorSpec).apply {
setBounds(0, 0, DimensionUnit.DP.toPixels(16f).toInt(), DimensionUnit.DP.toPixels(16f).toInt())
}
}
}
}

Wyświetl plik

@ -0,0 +1,23 @@
package org.thoughtcrime.securesms.util
import android.os.Bundle
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import java.util.function.Consumer
/**
* Generic Fragment result contract.
*/
abstract class FragmentResultContract<T> protected constructor(private val resultKey: String) {
protected abstract fun getResult(bundle: Bundle): T
fun registerForResult(fragmentManager: FragmentManager, lifecycleOwner: LifecycleOwner, consumer: Consumer<T>) {
fragmentManager.setFragmentResultListener(resultKey, lifecycleOwner) { key, bundle ->
if (key == resultKey) {
val result = getResult(bundle)
consumer.accept(result)
}
}
}
}

Wyświetl plik

@ -0,0 +1,20 @@
package org.thoughtcrime.securesms.util.adapter.mapping
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
/**
* Allows ViewHolders to be generated with a ViewBinding. Intended usage is as follows:
*
* BindingFactory(::MyBindingViewHolder, MyBinding::inflate)
*/
class BindingFactory<T : MappingModel<T>, B : ViewBinding>(
private val creator: (B) -> BindingViewHolder<T, B>,
private val inflater: (LayoutInflater, ViewGroup, Boolean) -> B
) : Factory<T> {
override fun createViewHolder(parent: ViewGroup): MappingViewHolder<T> {
val binding = inflater(LayoutInflater.from(parent.context), parent, false)
return creator(binding)
}
}

Wyświetl plik

@ -0,0 +1,8 @@
package org.thoughtcrime.securesms.util.adapter.mapping
import androidx.viewbinding.ViewBinding
/**
* A ViewHolder which is populated with a ViewBinding, used in conjunction with BindingFactory
*/
abstract class BindingViewHolder<T, B : ViewBinding>(protected val binding: B) : MappingViewHolder<T>(binding.root)

Wyświetl plik

@ -13,7 +13,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject
*/
class RxStore<T : Any>(
defaultValue: T,
private val scheduler: Scheduler = Schedulers.computation()
scheduler: Scheduler = Schedulers.computation()
) {
private val behaviorProcessor = BehaviorProcessor.createDefault(defaultValue)

Wyświetl plik

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/signal_colorOutline" />
</shape>

Wyświetl plik

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton 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"
style="@style/Signal.Widget.Button.Base.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:minHeight="56dp"
android:paddingHorizontal="16dp"
android:textAlignment="viewStart"
android:textColor="@color/signal_colorOnSurface"
app:cornerRadius="12dp"
app:icon="@drawable/ic_copy_24"
app:iconGravity="end"
app:iconSize="24dp"
app:iconTint="@color/signal_colorOnSurface"
app:rippleColor="@color/signal_colorOutline"
app:strokeColor="@color/signal_colorOutline_38"
app:strokeWidth="1dp"
tools:text="maya#2342" />

Wyświetl plik

@ -1,297 +1,310 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
tools:viewBindingIgnore="true"
<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="@dimen/signal_m3_toolbar_height"
android:minHeight="@dimen/signal_m3_toolbar_height"
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="80dp"
android:layout_height="80dp"
android:layout_marginTop="16dp"
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" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_avatar_initials"
android:layout_width="0dp"
android:layout_height="0dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:visibility="gone"
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"
tools:ignore="SpUsage"
tools:text="AF"
tools:visibility="visible" />
<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" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/manage_profile_badge"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="44dp"
android:layout_marginTop="52dp"
app:badge_size="large"
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background" />
<com.google.android.material.button.MaterialButton
android:id="@+id/manage_profile_edit_photo"
style="@style/Widget.Signal.Button.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="14dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:text="@string/ManageProfileFragment__edit_photo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manage_profile_avatar_background" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manage_profile_name_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="?selectableItemBackground"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/manage_profile_edit_photo">
<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"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
style="@style/Signal.Text.Body"
android:textAlignment="viewStart"
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="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="?selectableItemBackground"
app:layout_constraintTop_toBottomOf="@id/manage_profile_name_container">
android:layout_height="wrap_content">
<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"/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/signal_m3_toolbar_height"
android:minHeight="@dimen/signal_m3_toolbar_height"
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" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
style="@style/Signal.Text.Body"
android:textAlignment="viewStart"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_username_icon"
app:layout_constraintEnd_toEndOf="parent"
tools:text="\@spiderman"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/manage_profile_avatar_background"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginTop="16dp"
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" />
<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.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" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_avatar_initials"
android:layout_width="0dp"
android:layout_height="0dp"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:visibility="gone"
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"
tools:ignore="SpUsage"
tools:text="AF"
tools:visibility="visible" />
<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" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/manage_profile_badge"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="44dp"
android:layout_marginTop="52dp"
app:badge_size="large"
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background" />
<com.google.android.material.button.MaterialButton
android:id="@+id/manage_profile_edit_photo"
style="@style/Widget.Signal.Button.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="14dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:text="@string/ManageProfileFragment__edit_photo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/manage_profile_avatar_background" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manage_profile_name_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="?selectableItemBackground"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/manage_profile_edit_photo">
<ImageView
android:id="@+id/manage_profile_name_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/manage_profile_name_subtitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/manage_profile_name"
app:srcCompat="@drawable/ic_profile_name_24"
app:tint="@color/signal_text_primary" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_name"
style="@style/Signal.Text.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_name_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Peter Parker" />
<TextView
android:id="@+id/manage_profile_name_subtitle"
style="@style/Signal.Text.Preview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/ManageProfileFragment_your_name"
android:textColor="@color/signal_text_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/manage_profile_name"
app:layout_constraintTop_toBottomOf="@id/manage_profile_name" />
</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:background="?selectableItemBackground"
android:minHeight="72dp"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/safety_number_recipient_row_item_gutter"
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:layout_constraintBottom_toBottomOf="@id/manage_profile_username_subtitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/manage_profile_username"
app:srcCompat="@drawable/ic_at_24"
app:tint="@color/signal_text_primary" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_username"
style="@style/Signal.Text.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAlignment="viewStart"
app:layout_constraintBottom_toTopOf="@id/manage_profile_username_subtitle"
app:layout_constraintEnd_toStartOf="@id/manage_profile_username_share"
app:layout_constraintStart_toEndOf="@id/manage_profile_username_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginEnd="48dp"
tools:text="\@spiderman" />
<TextView
android:id="@+id/manage_profile_username_subtitle"
style="@style/Signal.Text.Preview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/ManageProfileFragment_your_username"
android:textColor="@color/signal_text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/manage_profile_username_share"
app:layout_constraintStart_toStartOf="@id/manage_profile_username"
app:layout_constraintTop_toBottomOf="@id/manage_profile_username"
app:layout_goneMarginEnd="48dp" />
<ImageView
android:id="@+id/manage_profile_username_share"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="4dp"
android:background="?selectableItemBackground"
android:scaleType="centerInside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_share_24"
app:tint="@color/signal_colorOnSurface" />
</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:background="?selectableItemBackground"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/manage_profile_username_container">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/manage_profile_about_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@id/manage_profile_about_subtitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/manage_profile_about"
app:srcCompat="@drawable/ic_compose_24"
app:tint="@color/signal_text_primary" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_about"
style="@style/Signal.Text.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textAlignment="viewStart"
app:emoji_forceCustom="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_about_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Photographer for the Daily Bugle" />
<TextView
android:id="@+id/manage_profile_about_subtitle"
style="@style/Signal.Text.Preview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/ManageProfileFragment_write_a_few_words_about_yourself"
android:textColor="@color/signal_text_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/manage_profile_about"
app:layout_constraintTop_toBottomOf="@id/manage_profile_about" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manage_profile_badges_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/manage_profile_about_container">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/manage_profile_badges_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@id/manage_profile_badges"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/manage_profile_badges"
app:srcCompat="@drawable/ic_badge_24"
app:tint="@color/signal_text_primary" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_badges"
style="@style/Signal.Text.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="@string/ManageProfileFragment_badges"
android:textAlignment="viewStart"
app:emoji_forceCustom="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_badges_icon"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/group_description_text"
style="@style/Signal.Text.Preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="16dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:layout_marginBottom="16dp"
android:text="@string/CreateProfileActivity_signal_profiles_are_end_to_end_encrypted"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manage_profile_badges_container"
app:layout_constraintVertical_bias="1.0" />
</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="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="?selectableItemBackground"
app:layout_constraintTop_toBottomOf="@id/manage_profile_username_container">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/manage_profile_about_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="fitCenter"
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"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_about"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
style="@style/Signal.Text.Body"
android:textAlignment="viewStart"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_about_icon"
app:layout_constraintEnd_toEndOf="parent"
app:emoji_forceCustom="true"
tools:text="Photographer 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>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/manage_profile_badges_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="?selectableItemBackground"
app:layout_constraintTop_toBottomOf="@id/manage_profile_about_container">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/manage_profile_badges_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_badge_24"
app:tint="@color/signal_text_primary"
app:layout_constraintTop_toTopOf="@id/manage_profile_badges"
app:layout_constraintBottom_toBottomOf="@id/manage_profile_badges"
app:layout_constraintStart_toStartOf="parent"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/manage_profile_badges"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
style="@style/Signal.Text.Body"
android:textAlignment="viewStart"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/manage_profile_badges_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="@string/ManageProfileFragment_badges"
app:emoji_forceCustom="true" />
</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="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:text="@string/CreateProfileActivity_signal_profiles_are_end_to_end_encrypted"
android:textColor="@color/signal_colorOnSurfaceVariant"
android:textAppearance="@style/Signal.Text.BodyMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/manage_profile_badges_container"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

Wyświetl plik

@ -2,7 +2,6 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -37,13 +36,12 @@
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Signal.Title2.Bold"
android:layout_width="wrap_content"
style="@style/Signal.Text.TitleLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="8dp"
android:paddingHorizontal="@dimen/dsl_settings_gutter"
android:text="@string/CreateProfileActivity_set_up_your_profile"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
@ -52,15 +50,28 @@
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="spread_inside" />
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/profile_description_text"
style="@style/Signal.Text.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="11dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:layout_marginBottom="16dp"
android:text="@string/ProfileCreateFragment__profiles_are_only_visible_to_people_you_message"
android:textColor="@color/core_grey_60"
app:layout_constraintTop_toBottomOf="@id/title" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/avatar_background"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginTop="16dp"
android:src="@drawable/circle_tintable"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/profile_description_text"
app:layout_goneMarginTop="?attr/actionBarSize" />
<androidx.appcompat.widget.AppCompatImageView
@ -100,8 +111,8 @@
android:id="@+id/camera_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="56dp"
android:layout_marginTop="56dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:background="@drawable/circle_tintable_padded"
android:cropToPadding="false"
android:elevation="4dp"
@ -122,66 +133,131 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name_preview">
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/given_name"
style="@style/Signal.Text.Body"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/given_name_wrapper"
style="@style/Widget.Signal.TextInputLayout.FilledBox.ContactNameEditor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginTop="13dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:autofillHints="personGivenName"
android:hint="@string/CreateProfileActivity_first_name_required"
android:inputType="textPersonName"
android:singleLine="true" />
android:hint="@string/CreateProfileActivity_first_name_required">
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/given_name"
style="@style/Signal.Text.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:autofillHints="personGivenName"
android:inputType="textPersonName"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/mms_group_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:text="@string/CreateProfileActivity_custom_mms_group_names_and_photos_will_only_be_visible_to_you"
android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/signal_text_secondary"
android:visibility="gone"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/family_name"
style="@style/Signal.Text.Body"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/family_name_wrapper"
style="@style/Widget.Signal.TextInputLayout.FilledBox.ContactNameEditor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginTop="13dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:autofillHints="personFamilyName"
android:hint="@string/CreateProfileActivity_last_name_optional"
android:inputType="textPersonName"
android:singleLine="true" />
android:hint="@string/CreateProfileActivity_last_name_optional">
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
android:id="@+id/family_name"
style="@style/Signal.Text.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:autofillHints="personFamilyName"
android:inputType="textPersonName"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/description_text"
style="@style/Signal.Text.Preview"
android:id="@+id/group_description_text"
style="@style/Signal.Text.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="11dp"
android:layout_marginEnd="16dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:layout_marginBottom="16dp"
android:text="@string/CreateProfileActivity_signal_profiles_are_end_to_end_encrypted"
android:text="@string/CreateProfileActivity_group_descriptions_will_be_visible_to_members_of_this_group_and_people_who_have_been_invited"
android:textColor="@color/core_grey_60"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name_container"
app:layout_constraintVertical_bias="1.0" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/who_can_find_me_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="23dp"
android:background="?selectableItemBackground"
android:minHeight="72dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/group_description_text"
tools:visibility="visible">
<ImageView
android:id="@+id/who_can_find_me_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:importantForAccessibility="no"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_group_24" />
<TextView
android:id="@+id/who_can_find_me_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:text="@string/ProfileCreateFragment__who_can_find_me"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/signal_colorOnSurface"
app:layout_constraintBottom_toTopOf="@id/who_can_find_me_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/who_can_find_me_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/who_can_find_me_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/who_can_find_me_icon"
app:layout_constraintTop_toBottomOf="@id/who_can_find_me_title"
tools:text="@string/PhoneNumberPrivacy_everyone" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
@ -196,8 +272,8 @@
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:enabled="false"
app:materialThemeOverlay="@style/ThemeOverlay.Signal.CircularProgressIndicator.Primary"
app:circularProgressMaterialButton__label="@string/CreateProfileActivity_next" />
app:circularProgressMaterialButton__label="@string/CreateProfileActivity_next"
app:materialThemeOverlay="@style/ThemeOverlay.Signal.CircularProgressIndicator.Primary" />
</LinearLayout>

Wyświetl plik

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/share_button"
style="@style/Signal.Widget.Button.Medium.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/preferences_share"
app:icon="@drawable/ic_share_24"
app:iconSize="20dp" />
</FrameLayout>

Wyświetl plik

@ -1,7 +1,6 @@
<?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"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -10,59 +9,94 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_height="@dimen/signal_m3_toolbar_height"
android:minHeight="@dimen/signal_m3_toolbar_height"
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/UsernameEditFragment_username" />
app:navigationIcon="@drawable/ic_x_24"
app:title="@string/UsernameEditFragment_username"
app:titleTextAppearance="@style/Signal.Text.TitleLarge" />
<EditText
android:id="@+id/username_text"
style="@style/Signal.Text.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:hint="@string/UsernameEditFragment_username"
android:imeOptions="actionDone"
android:importantForAutofill="no"
android:inputType="text"
android:maxLines="1"
android:minHeight="56dp"
<ImageView
android:id="@+id/icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="32dp"
android:background="@drawable/circle_tintable"
android:importantForAccessibility="no"
android:padding="14dp"
app:backgroundTint="@color/signal_colorSurface2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:srcCompat="@drawable/ic_at_24" />
<TextView
android:id="@+id/username_subtext"
style="@style/Signal.Text.Caption"
android:layout_width="0dp"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_text_wrapper"
style="@style/Widget.Signal.TextInputLayout.FilledBox.ContactNameEditor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="24dp"
app:boxStrokeColor="@color/signal_colorPrimary"
app:boxStrokeWidthFocused="2dp"
app:expandedHintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/username_text"
tools:text="Some error code" />
app:layout_constraintTop_toBottomOf="@id/summary"
app:suffixTextColor="@color/signal_colorOnSurface"
tools:suffixText="| #1234">
<EditText
android:id="@+id/username_text"
style="@style/Signal.Text.Body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/UsernameEditFragment_username"
android:imeOptions="actionDone"
android:importantForAutofill="no"
android:inputType="text"
android:maxLines="1"
android:minHeight="56dp">
<requestFocus />
</EditText>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="12dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:gravity="center"
android:text="@string/UsernameEditFragment__choose_your_username"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/icon" />
<org.thoughtcrime.securesms.util.views.LearnMoreTextView
android:id="@+id/username_description"
style="@style/Signal.Text.Caption"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="@string/UsernameEditFragment_usernames_on_signal_are_optional"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:layout_marginTop="24dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:layout_marginBottom="24dp"
android:text="@string/UsernameEditFragment__usernames_let_others_message"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintBottom_toTopOf="@id/username_button_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/username_subtext" />
app:layout_constraintTop_toBottomOf="@id/username_text_wrapper"
app:layout_constraintVertical_bias="0" />
<org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
android:id="@+id/username_submit_button"

Wyświetl plik

@ -37,6 +37,14 @@
app:nullable="true" />
</action>
<action
android:id="@+id/action_createProfileFragment_to_phoneNumberPrivacy"
app:destination="@id/phoneNumberPrivacy"
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
@ -47,4 +55,10 @@
<include app:graph="@navigation/avatar_picker" />
<fragment
android:id="@+id/phoneNumberPrivacy"
android:name="org.thoughtcrime.securesms.profiles.edit.pnp.WhoCanSeeMyPhoneNumberFragment"
android:label="fragment_phone_number_privacy"
tools:layout="@layout/dsl_settings_fragment" />
</navigation>

Wyświetl plik

@ -62,6 +62,9 @@
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_manageProfileFragment_to_shareUsernameDialog"
app:destination="@id/shareUsernameDialog" />
</fragment>
@ -87,4 +90,9 @@
<include app:graph="@navigation/avatar_picker" />
<dialog
android:id="@+id/shareUsernameDialog"
android:name="org.thoughtcrime.securesms.profiles.manage.UsernameShareBottomSheet"
android:label="fragment_username_share" />
</navigation>

Wyświetl plik

@ -11,6 +11,7 @@
<string name="sustainer_boost_and_badges" translatable="false">https://support.signal.org/hc/articles/4408365318426</string>
<string name="google_pay_url" translatable="false">https://pay.google.com</string>
<string name="donation_decline_code_error_url" translatable="false">https://support.signal.org/hc/articles/4408365318426#errors</string>
<string name="signal_me_url" translatable="false">https://signal.me/%1$s</string>
<string name="yes">Yes</string>
<string name="no">No</string>
@ -503,6 +504,20 @@
<string name="CreateProfileActivity_signal_profiles_are_end_to_end_encrypted">Your profile is end-to-end encrypted. Your profile and changes to it will be visible to your contacts, when you initiate or accept new conversations, and when you join new groups.</string>
<string name="CreateProfileActivity_set_avatar_description">Set avatar</string>
<!-- ProfileCreateFragment -->
<!-- Displayed at the top of the screen and explains how profiles can be viewed. -->
<string name="ProfileCreateFragment__profiles_are_only_visible_to_people_you_message">Profiles are only visible to people you message.</string>
<!-- Title of clickable row to select phone number privacy settings -->
<string name="ProfileCreateFragment__who_can_find_me">Who can find me by number?</string>
<!-- WhoCanSeeMyPhoneNumberFragment -->
<!-- Toolbar title for this screen -->
<string name="WhoCanSeeMyPhoneNumberFragment__who_can_find_me_by_number">Who can find me by number?</string>
<!-- Description for radio item stating anyone can see your phone number -->
<string name="WhoCanSeeMyPhoneNumberFragment__anyone_who_has">Anyone who has your phone number in their contacts will see you as a contact on Signal. Others will be able to find you with your number in search.</string>
<!-- Description for radio item stating no one will be able to see your phone number -->
<string name="WhoCanSeeMyPhoneNumberFragment__nobody_on_signal">Nobody on Signal will be able to find you with your phone number.</string>
<!-- ChooseBackupFragment -->
<string name="ChooseBackupFragment__restore_from_backup">Restore from backup?</string>
<string name="ChooseBackupFragment__restore_your_messages_and_media">Restore your messages and media from a local backup. If you don\'t restore now, you won\'t be able to restore later.</string>
@ -856,6 +871,11 @@
<string name="ManageProfileFragment_failed_to_set_avatar">Failed to set avatar</string>
<string name="ManageProfileFragment_badges">Badges</string>
<string name="ManageProfileFragment__edit_photo">Edit photo</string>
<!-- Snackbar message after creating username -->
<string name="ManageProfileFragment__username_created">Username created</string>
<!-- Snackbar message after copying username -->
<string name="ManageProfileFragment__username_copied">Username copied</string>
<!-- ManageRecipientActivity -->
<string name="ManageRecipientActivity_no_groups_in_common">No groups in common</string>
@ -1851,6 +1871,8 @@
<string name="UnverifiedSendDialog_send">Send</string>
<!-- UsernameEditFragment -->
<!-- Instructional text at the top of the username edit screen -->
<string name="UsernameEditFragment__choose_your_username">Choose your username</string>
<string name="UsernameEditFragment_username">Username</string>
<string name="UsernameEditFragment_delete">Delete</string>
<string name="UsernameEditFragment_successfully_set_username">Successfully set username.</string>
@ -1862,13 +1884,21 @@
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Usernames cannot begin with a number.</string>
<string name="UsernameEditFragment_username_is_invalid">Username is invalid.</string>
<string name="UsernameEditFragment_usernames_must_be_between_a_and_b_characters">Usernames must be between %1$d and %2$d characters.</string>
<string name="UsernameEditFragment_usernames_on_signal_are_optional">Usernames on Signal are optional. If you choose to create a username, other Signal users will be able to find you by this username and contact you without knowing your phone number.</string>
<!-- Explanation about what usernames provide -->
<string name="UsernameEditFragment__usernames_let_others_message">Usernames let others message you without needing your phone number. They are paired with a set of digits to help keep your address private.</string>
<!-- Dialog title for explanation about numbers at the end of the username -->
<string name="UsernameEditFragment__what_is_this_number">What is this number?</string>
<string name="UsernameEditFragment__these_digits_help_keep">These digits help keep your username private so you can avoid unwanted messages. Share your username with only the people and groups you\'d like to chat with. If you change usernames you\'ll get a new set of digits.</string>
<plurals name="UserNotificationMigrationJob_d_contacts_are_on_signal">
<item quantity="one">%d contact is on Signal!</item>
<item quantity="other">%d contacts are on Signal!</item>
</plurals>
<!-- UsernameShareBottomSheet -->
<!-- Explanation of what the sheet enables the user to do -->
<string name="UsernameShareBottomSheet__copy_or_share_a_username_link">Copy or share a username link</string>
<!-- VerifyIdentityActivity -->
<string name="VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal">Your contact is running an old version of Signal. Please ask them to update before verifying your safety number.</string>
<string name="VerifyIdentityActivity_your_contact_is_running_a_newer_version_of_Signal">Your contact is running a newer version of Signal with an incompatible QR code format. Please update to compare.</string>