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 @@