From 083301185cf3b2e0ed4189f0c00cd231e271c051 Mon Sep 17 00:00:00 2001 From: Lucio Maciel Date: Fri, 27 Aug 2021 15:56:54 -0300 Subject: [PATCH] Update verify identity UI. --- .../securesms/VerifyIdentityActivity.java | 125 ++++++++++------ .../securesms/components/ShapeScrim.java | 32 +++- .../main/res/drawable/ic_camera_outline.xml | 39 +++++ .../main/res/drawable/qr_code_background.xml | 5 +- .../res/layout/verify_display_fragment.xml | 141 ++++++++++-------- .../main/res/layout/verify_scan_fragment.xml | 53 +++++-- app/src/main/res/values/strings.xml | 10 +- app/src/main/res/values/styles.xml | 2 +- 8 files changed, 283 insertions(+), 124 deletions(-) create mode 100644 app/src/main/res/drawable/ic_camera_outline.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 6ef67a5fd6..2ca59a9fed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -45,10 +45,10 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnticipateInterpolator; -import android.view.animation.OvershootInterpolator; import android.view.animation.ScaleAnimation; -import android.widget.CompoundButton; +import android.widget.Button; import android.widget.ImageView; +import android.widget.TextSwitcher; import android.widget.TextView; import android.widget.Toast; @@ -56,12 +56,16 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SwitchCompat; +import androidx.core.view.OneShotPreDrawListener; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +import androidx.transition.TransitionManager; import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.components.ShapeScrim; import org.thoughtcrime.securesms.components.camera.CameraView; import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; @@ -214,7 +218,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } - public static class VerifyDisplayFragment extends Fragment implements CompoundButton.OnCheckedChangeListener { + public static class VerifyDisplayFragment extends Fragment { public static final String RECIPIENT_ID = "recipient_id"; public static final String REMOTE_NUMBER = "remote_number"; @@ -230,23 +234,28 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement private View container; private View numbersContainer; + private View loading; + private View qrCodeContainer; private ImageView qrCode; private ImageView qrVerified; - private TextView tapLabel; + private TextSwitcher tapLabel; private TextView description; private View.OnClickListener clickListener; - private SwitchCompat verified; + private Button verifyButton; private TextView[] codes = new TextView[12]; private boolean animateSuccessOnDraw = false; private boolean animateFailureOnDraw = false; + private boolean currentVerifiedState = false; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment); this.numbersContainer = container.findViewById(R.id.number_table); + this.loading = container.findViewById(R.id.loading); + this.qrCodeContainer = container.findViewById(R.id.qr_code_container); this.qrCode = container.findViewById(R.id.qr_code); - this.verified = container.findViewById(R.id.verified_switch); + this.verifyButton = container.findViewById(R.id.verify_button); this.qrVerified = container.findViewById(R.id.qr_verified); this.description = container.findViewById(R.id.description); this.tapLabel = container.findViewById(R.id.tap_label); @@ -263,11 +272,11 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement this.codes[10] = container.findViewById(R.id.code_eleventh); this.codes[11] = container.findViewById(R.id.code_twelth); - this.qrCode.setOnClickListener(clickListener); + this.qrCodeContainer.setOnClickListener(clickListener); this.registerForContextMenu(numbersContainer); - this.verified.setChecked(getArguments().getBoolean(VERIFIED_STATE, false)); - this.verified.setOnCheckedChangeListener(this); + updateVerifyButton(getArguments().getBoolean(VERIFIED_STATE, false), false); + this.verifyButton.setOnClickListener((button -> updateVerifyButton(!currentVerifiedState, true))); return container; } @@ -327,6 +336,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement @Override protected void onPostExecute(Fingerprint fingerprint) { + if (getActivity() == null) return; VerifyDisplayFragment.this.fingerprint = fingerprint; setFingerprintViews(fingerprint, true); getActivity().supportInvalidateOptionsMenu(); @@ -480,7 +490,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement } private void setRecipientText(Recipient recipient) { - description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.getDisplayName(getContext())))); + description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.getDisplayName(getContext())))); description.setMovementMethod(LinkMovementMethod.getInstance()); } @@ -501,9 +511,11 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement if (animate) { ViewUtil.fadeIn(qrCode, 1000); ViewUtil.fadeIn(tapLabel, 1000); + ViewUtil.fadeOut(loading, 300, View.GONE); } else { qrCode.setVisibility(View.VISIBLE); tapLabel.setVisibility(View.VISIBLE); + loading.setVisibility(View.GONE); } } @@ -559,6 +571,8 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement qrVerified.setImageBitmap(qrSuccess); qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.MULTIPLY); + tapLabel.setText(getString(R.string.verify_display_fragment__successful_match)); + animateVerified(); } @@ -569,6 +583,8 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement qrVerified.setImageBitmap(qrSuccess); qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.MULTIPLY); + tapLabel.setText(getString(R.string.verify_display_fragment__failed_to_verify_safety_number)); + animateVerified(); } @@ -576,7 +592,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f); - scaleAnimation.setInterpolator(new OvershootInterpolator()); + scaleAnimation.setInterpolator(new FastOutSlowInInterpolator()); scaleAnimation.setDuration(800); scaleAnimation.setAnimationListener(new Animation.AnimationListener() { @Override @@ -594,6 +610,9 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement scaleAnimation.setInterpolator(new AnticipateInterpolator()); scaleAnimation.setDuration(500); ViewUtil.animateOut(qrVerified, scaleAnimation, View.GONE); + ViewUtil.fadeIn(qrCode, 800); + qrCodeContainer.setEnabled(true); + tapLabel.setText(getString(R.string.verify_display_fragment__tap_to_scan)); } }, 2000); } @@ -602,53 +621,75 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement public void onAnimationRepeat(Animation animation) {} }); + ViewUtil.fadeOut(qrCode, 200, View.INVISIBLE); ViewUtil.animateIn(qrVerified, scaleAnimation); + qrCodeContainer.setEnabled(false); } - @Override - public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) { - final Recipient recipient = this.recipient.get(); - final RecipientId recipientId = recipient.getId(); + private void updateVerifyButton(boolean verified, boolean update) { + currentVerifiedState = verified; - SignalExecutors.BOUNDED.execute(() -> { - try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - if (isChecked) { - Log.i(TAG, "Saving identity: " + recipientId); - DatabaseFactory.getIdentityDatabase(getActivity()) - .saveIdentity(recipientId, - remoteIdentity, - VerifiedStatus.VERIFIED, false, - System.currentTimeMillis(), true); - } else { - DatabaseFactory.getIdentityDatabase(getActivity()) - .setVerified(recipientId, - remoteIdentity, - VerifiedStatus.DEFAULT); + if (verified) { + verifyButton.setText(R.string.verify_display_fragment__clear_verification); + } else { + verifyButton.setText(R.string.verify_display_fragment__mark_as_verified); + } + + if (update) { + final RecipientId recipientId = recipient.getId(); + + SignalExecutors.BOUNDED.execute(() -> { + try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { + if (verified) { + Log.i(TAG, "Saving identity: " + recipientId); + DatabaseFactory.getIdentityDatabase(getActivity()) + .saveIdentity(recipientId, + remoteIdentity, + VerifiedStatus.VERIFIED, false, + System.currentTimeMillis(), true); + } else { + DatabaseFactory.getIdentityDatabase(getActivity()) + .setVerified(recipientId, + remoteIdentity, + VerifiedStatus.DEFAULT); + } + + ApplicationDependencies.getJobManager() + .add(new MultiDeviceVerifiedUpdateJob(recipientId, + remoteIdentity, + verified ? VerifiedStatus.VERIFIED + : VerifiedStatus.DEFAULT)); + StorageSyncHelper.scheduleSyncForDataChange(); + + IdentityUtil.markIdentityVerified(getActivity(), recipient.get(), verified, false); } - - ApplicationDependencies.getJobManager() - .add(new MultiDeviceVerifiedUpdateJob(recipientId, - remoteIdentity, - isChecked ? VerifiedStatus.VERIFIED - : VerifiedStatus.DEFAULT)); - StorageSyncHelper.scheduleSyncForDataChange(); - - IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false); - } - }); + }); + } } + } public static class VerifyScanFragment extends Fragment { private View container; private CameraView cameraView; + private ShapeScrim cameraScrim; + private ImageView cameraMarks; private ScanningThread scanningThread; private ScanListener scanListener; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { - this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment); - this.cameraView = container.findViewById(R.id.scanner); + this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment); + this.cameraView = container.findViewById(R.id.scanner); + this.cameraScrim = container.findViewById(R.id.camera_scrim); + this.cameraMarks = container.findViewById(R.id.camera_marks); + + OneShotPreDrawListener.add(cameraScrim, () -> { + int width = cameraScrim.getScrimWidth(); + int height = cameraScrim.getScrimHeight(); + + ViewUtil.updateLayoutParams(cameraMarks, width, height); + }); return container; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ShapeScrim.java b/app/src/main/java/org/thoughtcrime/securesms/components/ShapeScrim.java index 20055c0599..9c18249925 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ShapeScrim.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ShapeScrim.java @@ -23,9 +23,12 @@ public class ShapeScrim extends View { private final Paint eraser; private final ShapeType shape; private final float radius; + private final int canvasColor; private Bitmap scrim; private Canvas scrimCanvas; + private int scrimWidth; + private int scrimHeight; public ShapeScrim(Context context) { this(context, null); @@ -57,13 +60,30 @@ public class ShapeScrim extends View { this.eraser = new Paint(); this.eraser.setColor(0xFFFFFFFF); this.eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + + this.canvasColor = Color.parseColor("#55BDBDBD"); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + int shortDimension = Math.min(getWidth(), getHeight()); + float drawRadius = shortDimension * radius; + + float left = (getMeasuredWidth() / 2 ) - drawRadius; + float top = (getMeasuredHeight() / 2) - drawRadius; + float right = left + (drawRadius * 2); + float bottom = top + (drawRadius * 2); + + scrimWidth = (int) (right - left); + scrimHeight = (int) (bottom - top); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); - int shortDimension = getWidth() < getHeight() ? getWidth() : getHeight(); + int shortDimension = Math.min(getWidth(), getHeight()); float drawRadius = shortDimension * radius; if (scrimCanvas == null) { @@ -72,7 +92,7 @@ public class ShapeScrim extends View { } scrim.eraseColor(Color.TRANSPARENT); - scrimCanvas.drawColor(Color.parseColor("#55BDBDBD")); + scrimCanvas.drawColor(canvasColor); if (shape == ShapeType.CIRCLE) drawCircle(scrimCanvas, drawRadius, eraser); else drawSquare(scrimCanvas, drawRadius, eraser); @@ -104,4 +124,12 @@ public class ShapeScrim extends View { canvas.drawRoundRect(square, 25, 25, eraser); } + + public int getScrimWidth() { + return scrimWidth; + } + + public int getScrimHeight() { + return scrimHeight; + } } diff --git a/app/src/main/res/drawable/ic_camera_outline.xml b/app/src/main/res/drawable/ic_camera_outline.xml new file mode 100644 index 0000000000..0f645e1898 --- /dev/null +++ b/app/src/main/res/drawable/ic_camera_outline.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/qr_code_background.xml b/app/src/main/res/drawable/qr_code_background.xml index a5266a706e..8cc4cc3072 100644 --- a/app/src/main/res/drawable/qr_code_background.xml +++ b/app/src/main/res/drawable/qr_code_background.xml @@ -1,5 +1,6 @@ - + android:shape="rectangle"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/verify_display_fragment.xml b/app/src/main/res/layout/verify_display_fragment.xml index 773c650864..9e8729606c 100644 --- a/app/src/main/res/layout/verify_display_fragment.xml +++ b/app/src/main/res/layout/verify_display_fragment.xml @@ -1,66 +1,91 @@ - - - + android:orientation="vertical" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" + android:background="@drawable/qr_code_background"> - + + + + + tools:src="@drawable/ic_about_mc_80" + tools:visibility="visible" /> - - - + android:visibility="gone" + android:layout_gravity="center" + tools:visibility="visible"/> + + - + + + + + + @@ -155,34 +180,28 @@ - - - - - - - - - + android:text="@string/verify_display_fragment__to_verify_the_security_of_your_end_to_end_encryption_with_s" + style="@style/TextAppearance.Signal.Body2" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/verify_scan_fragment.xml b/app/src/main/res/layout/verify_scan_fragment.xml index d379c60023..a66909251f 100644 --- a/app/src/main/res/layout/verify_scan_fragment.xml +++ b/app/src/main/res/layout/verify_scan_fragment.xml @@ -1,27 +1,52 @@ - + + + + android:layout_height="0dp" + app:camera="0" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@+id/information" /> - + + + + app:shape="square" /> + - - - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be1e9de02f..9ef47ef3b3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2153,14 +2153,20 @@ Enter a name or number - Learn more.]]> + Learn more.]]> Tap to scan + Successful match + Failed to verify safety number Loading… - Verified + Mark as verified + Clear verification Share safety number + + Scan the QR Code on your contact\’s device. + Swipe up to answer Swipe down to reject diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index bce3c03c42..9703e1d4a0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -311,7 +311,7 @@