mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Fix several issues with new avatar picker.
* Fix silliness with text behaviour * Fix long click behaviour * Make views play nicer with landscape mode * Do not show megaphone if user has an avatar (or had one and removed it) * Fix bad heading on vector color picker
This commit is contained in:
committed by
Greyson Parrelli
parent
ab56856f41
commit
c1b54b3532
@@ -160,8 +160,8 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
|
||||
val menuRes = when (avatar) {
|
||||
is Avatar.Photo -> R.menu.avatar_picker_context
|
||||
is Avatar.Text -> R.menu.avatar_picker_context
|
||||
is Avatar.Vector -> return false
|
||||
is Avatar.Resource -> return false
|
||||
is Avatar.Vector -> return true
|
||||
is Avatar.Resource -> return true
|
||||
}
|
||||
|
||||
val popup = PopupMenu(context, anchorView, Gravity.TOP)
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.avatar.picker
|
||||
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
@@ -57,14 +58,17 @@ object AvatarPickerItem {
|
||||
init {
|
||||
textView.typeface = AvatarRenderer.getTypeface(context)
|
||||
textView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||
updateAndApplyText(textView.text.toString())
|
||||
updateFontSize(textView.text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAndApplyText(text: String) {
|
||||
private fun updateFontSize(text: String) {
|
||||
val textSize = Avatars.getTextSizeForLength(context, text, textView.measuredWidth * 0.8f, textView.measuredHeight * 0.45f)
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
textView.text = text
|
||||
|
||||
if (textView !is EditText) {
|
||||
textView.text = text
|
||||
}
|
||||
}
|
||||
|
||||
override fun bind(model: Model) {
|
||||
@@ -76,12 +80,8 @@ object AvatarPickerItem {
|
||||
selectedOverlay?.animate()?.cancel()
|
||||
selectedFader?.animate()?.cancel()
|
||||
|
||||
if (model.isSelected) {
|
||||
itemView.setOnLongClickListener {
|
||||
onAvatarLongClickListener?.invoke(itemView, model.avatar) ?: false
|
||||
}
|
||||
} else {
|
||||
itemView.setOnLongClickListener(null)
|
||||
itemView.setOnLongClickListener {
|
||||
onAvatarLongClickListener?.invoke(itemView, model.avatar) ?: false
|
||||
}
|
||||
|
||||
itemView.setOnClickListener { onAvatarClickListener?.invoke(model.avatar, model.isSelected) }
|
||||
@@ -108,7 +108,10 @@ object AvatarPickerItem {
|
||||
is Avatar.Text -> {
|
||||
textView.visible = true
|
||||
|
||||
updateAndApplyText(model.avatar.text)
|
||||
updateFontSize(model.avatar.text)
|
||||
if (textView.text.toString() != model.avatar.text) {
|
||||
textView.text = model.avatar.text
|
||||
}
|
||||
|
||||
imageView.setImageDrawable(null)
|
||||
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
|
||||
|
||||
@@ -31,12 +31,13 @@ import org.thoughtcrime.securesms.util.ViewUtil
|
||||
/**
|
||||
* Fragment to create an avatar based off of a Vector or Text (via a pager)
|
||||
*/
|
||||
class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragment_hidden_recycler) {
|
||||
class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragment) {
|
||||
|
||||
private val viewModel: TextAvatarCreationViewModel by viewModels(factoryProducer = this::createFactory)
|
||||
|
||||
private lateinit var textInput: EditText
|
||||
private lateinit var recycler: RecyclerView
|
||||
private lateinit var content: ConstraintLayout
|
||||
|
||||
private val withRecyclerSet = ConstraintSet()
|
||||
private val withoutRecyclerSet = ConstraintSet()
|
||||
@@ -60,9 +61,10 @@ class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragme
|
||||
val tabLayout: ControllableTabLayout = view.findViewById(R.id.text_avatar_creation_tabs)
|
||||
val doneButton: View = view.findViewById(R.id.text_avatar_creation_done)
|
||||
|
||||
withRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment)
|
||||
withoutRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment_hidden_recycler)
|
||||
withRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment_content)
|
||||
withoutRecyclerSet.load(requireContext(), R.layout.text_avatar_creation_fragment_content_hidden_recycler)
|
||||
|
||||
content = view.findViewById(R.id.content)
|
||||
recycler = view.findViewById(R.id.text_avatar_creation_recycler)
|
||||
textInput = view.findViewById(R.id.avatar_picker_item_text)
|
||||
|
||||
@@ -83,19 +85,7 @@ class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragme
|
||||
val viewHolder = AvatarPickerItem.ViewHolder(view)
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
EditTextUtil.setCursorColor(textInput, state.currentAvatar.color.foregroundColor)
|
||||
|
||||
val hadText = textInput.length() > 0
|
||||
val selectionStart = textInput.selectionStart
|
||||
val selectionEnd = textInput.selectionEnd
|
||||
|
||||
viewHolder.bind(AvatarPickerItem.Model(state.currentAvatar, false))
|
||||
textInput.post {
|
||||
if (!hadText) {
|
||||
textInput.setSelection(textInput.length())
|
||||
} else {
|
||||
textInput.setSelection(selectionStart, selectionEnd)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.submitList(state.colors().map { AvatarColorItem.Model(it) })
|
||||
hasBoundFromViewModel = true
|
||||
@@ -114,8 +104,8 @@ class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragme
|
||||
}
|
||||
|
||||
textInput.setOnEditorActionListener { v, actionId, event ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
doneButton.performClick()
|
||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
tabLayout.getTabAt(1)?.select()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -130,20 +120,18 @@ class TextAvatarCreationFragment : Fragment(R.layout.text_avatar_creation_fragme
|
||||
textInput.isEnabled = true
|
||||
ViewUtil.focusAndShowKeyboard(textInput)
|
||||
|
||||
val constraintLayout = requireView() as ConstraintLayout
|
||||
TransitionManager.endTransitions(constraintLayout)
|
||||
withoutRecyclerSet.applyTo(constraintLayout)
|
||||
TransitionManager.beginDelayedTransition(constraintLayout)
|
||||
TransitionManager.endTransitions(content)
|
||||
withoutRecyclerSet.applyTo(content)
|
||||
TransitionManager.beginDelayedTransition(content)
|
||||
textInput.setSelection(textInput.length())
|
||||
}
|
||||
1 -> {
|
||||
textInput.isEnabled = false
|
||||
ViewUtil.hideKeyboard(requireContext(), textInput)
|
||||
|
||||
val constraintLayout = requireView() as ConstraintLayout
|
||||
TransitionManager.endTransitions(constraintLayout)
|
||||
withRecyclerSet.applyTo(constraintLayout)
|
||||
TransitionManager.beginDelayedTransition(constraintLayout)
|
||||
TransitionManager.endTransitions(content)
|
||||
withRecyclerSet.applyTo(content)
|
||||
TransitionManager.beginDelayedTransition(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,29 +14,33 @@ public class EmojiSpan extends AnimatingImageSpan {
|
||||
|
||||
private final float SHIFT_FACTOR = 1.5f;
|
||||
|
||||
private final int size;
|
||||
private final FontMetricsInt fm;
|
||||
private int size;
|
||||
private FontMetricsInt fontMetrics;
|
||||
|
||||
public EmojiSpan(@NonNull Drawable drawable, @NonNull TextView tv) {
|
||||
super(drawable, tv);
|
||||
fm = tv.getPaint().getFontMetricsInt();
|
||||
size = fm != null ? Math.abs(fm.descent) + Math.abs(fm.ascent)
|
||||
: tv.getResources().getDimensionPixelSize(R.dimen.conversation_item_body_text_size);
|
||||
fontMetrics = tv.getPaint().getFontMetricsInt();
|
||||
size = fontMetrics != null ? Math.abs(fontMetrics.descent) + Math.abs(fontMetrics.ascent)
|
||||
: tv.getResources().getDimensionPixelSize(R.dimen.conversation_item_body_text_size);
|
||||
getDrawable().setBounds(0, 0, size, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
|
||||
if (fm != null && this.fm != null) {
|
||||
fm.ascent = this.fm.ascent;
|
||||
fm.descent = this.fm.descent;
|
||||
fm.top = this.fm.top;
|
||||
fm.bottom = this.fm.bottom;
|
||||
fm.leading = this.fm.leading;
|
||||
return size;
|
||||
if (fm != null && this.fontMetrics != null) {
|
||||
fm.ascent = this.fontMetrics.ascent;
|
||||
fm.descent = this.fontMetrics.descent;
|
||||
fm.top = this.fontMetrics.top;
|
||||
fm.bottom = this.fontMetrics.bottom;
|
||||
fm.leading = this.fontMetrics.leading;
|
||||
} else {
|
||||
return super.getSize(paint, text, start, end, fm);
|
||||
this.fontMetrics = paint.getFontMetricsInt();
|
||||
this.size = Math.abs(this.fontMetrics.descent) + Math.abs(this.fontMetrics.ascent);
|
||||
|
||||
getDrawable().setBounds(0, 0, size, size);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -100,6 +101,10 @@ public class RetrieveProfileAvatarJob extends BaseJob {
|
||||
|
||||
try {
|
||||
AvatarHelper.setAvatar(context, recipient.getId(), avatarStream);
|
||||
|
||||
if (recipient.isSelf()) {
|
||||
SignalStore.misc().markHasEverHadAnAvatar();
|
||||
}
|
||||
} catch (AssertionError e) {
|
||||
throw new IOException("Failed to copy stream. Likely a Conscrypt issue.", e);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
||||
private static final String USERNAME_SHOW_REMINDER = "username.show.reminder";
|
||||
private static final String CLIENT_DEPRECATED = "misc.client_deprecated";
|
||||
private static final String OLD_DEVICE_TRANSFER_LOCKED = "misc.old_device.transfer.locked";
|
||||
private static final String HAS_EVER_HAD_AN_AVATAR = "misc.has.ever.had.an.avatar";
|
||||
|
||||
MiscellaneousValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
@@ -88,4 +89,12 @@ public final class MiscellaneousValues extends SignalStoreValues {
|
||||
public void clearOldDeviceTransferLocked() {
|
||||
putBoolean(OLD_DEVICE_TRANSFER_LOCKED, false);
|
||||
}
|
||||
|
||||
public boolean hasEverHadAnAvatar() {
|
||||
return getBoolean(HAS_EVER_HAD_AN_AVATAR, false);
|
||||
}
|
||||
|
||||
public void markHasEverHadAnAvatar() {
|
||||
putBoolean(HAS_EVER_HAD_AN_AVATAR, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public final class Megaphones {
|
||||
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
|
||||
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER);
|
||||
put(Event.CHAT_COLORS, ALWAYS);
|
||||
put(Event.ADD_A_PROFILE_PHOTO, shouldShowAddAProfileMegaphone(context) ? ALWAYS : NEVER);
|
||||
put(Event.ADD_A_PROFILE_PHOTO, shouldShowAddAProfilePhotoMegaphone(context) ? ALWAYS : NEVER);
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -383,8 +383,18 @@ public final class Megaphones {
|
||||
return shouldShow;
|
||||
}
|
||||
|
||||
private static boolean shouldShowAddAProfileMegaphone(@NonNull Context context) {
|
||||
return !AvatarHelper.hasAvatar(context, Recipient.self().getId());
|
||||
private static boolean shouldShowAddAProfilePhotoMegaphone(@NonNull Context context) {
|
||||
if (SignalStore.misc().hasEverHadAnAvatar()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasAnAvatar = AvatarHelper.hasAvatar(context, Recipient.self().getId());
|
||||
if (hasAnAvatar) {
|
||||
SignalStore.misc().markHasEverHadAnAvatar();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public enum Event {
|
||||
|
||||
@@ -253,7 +253,10 @@ public class EditProfileFragment extends LoggingFragment {
|
||||
|
||||
private void initializeProfileAvatar() {
|
||||
viewModel.avatar().observe(getViewLifecycleOwner(), bytes -> {
|
||||
if (bytes == null) return;
|
||||
if (bytes == null) {
|
||||
GlideApp.with(this).clear(avatar);
|
||||
return;
|
||||
}
|
||||
|
||||
GlideApp.with(this)
|
||||
.load(bytes)
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
@@ -146,6 +147,10 @@ public class EditSelfProfileRepository implements EditProfileRepository {
|
||||
|
||||
RegistrationUtil.maybeMarkRegistrationComplete(context);
|
||||
|
||||
if (avatar != null) {
|
||||
SignalStore.misc().markHasEverHadAnAvatar();
|
||||
}
|
||||
|
||||
return UploadResult.SUCCESS;
|
||||
}, uploadResultConsumer::accept);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.core.util.Consumer;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -53,6 +54,7 @@ final class ManageProfileRepository {
|
||||
try {
|
||||
ProfileUtil.uploadProfileWithAvatar(context, new StreamDetails(new ByteArrayInputStream(data), contentType, data.length));
|
||||
AvatarHelper.setAvatar(context, Recipient.self().getId(), new ByteArrayInputStream(data));
|
||||
SignalStore.misc().markHasEverHadAnAvatar();
|
||||
callback.accept(Result.SUCCESS);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to upload profile during avatar change.", e);
|
||||
|
||||
Reference in New Issue
Block a user